Flutter iOS Embedder
FlutterPlatformViewsController.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 #include "display_list/geometry/dl_geometry_types.h"
7 #include "impeller/geometry/rounding_radii.h"
8 
9 #include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h"
10 #include "flutter/display_list/geometry/dl_geometry_conversions.h"
11 #include "flutter/display_list/utils/dl_matrix_clip_tracker.h"
12 #include "flutter/flow/surface_frame.h"
13 #include "flutter/flow/view_slicer.h"
14 #include "flutter/fml/logging.h"
15 #include "flutter/fml/make_copyable.h"
16 #include "flutter/fml/synchronization/count_down_latch.h"
17 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
22 
23 using flutter::DlISize;
24 using flutter::DlMatrix;
25 using flutter::DlRect;
26 using flutter::DlRoundRect;
27 
28 static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
29 
30 static NSString* const kGestureBlockingPolicyEagerValue = @"eager";
31 static NSString* const kGestureBlockingPolicyWaitUntilTouchesEndedValue = @"waitUntilTouchesEnded";
32 static NSString* const kGestureBlockingPolicyDoNotBlockGesture = @"doNotBlockGesture";
33 static NSString* const kGestureBlockingPolicyFallbackToPluginDefault = @"fallbackToPluginDefault";
34 
35 struct LayerData {
36  DlRect rect;
37  int64_t view_id;
38  int64_t overlay_id;
39  std::shared_ptr<flutter::OverlayLayer> layer;
40 };
41 using LayersMap = std::unordered_map<int64_t, LayerData>;
42 
43 /// Each of the following structs stores part of the platform view hierarchy according to its
44 /// ID.
45 ///
46 /// This data must only be accessed on the platform thread.
48  NSObject<FlutterPlatformView>* view;
50  UIView* root_view;
51 };
52 
53 // Converts a DlMatrix to CATransform3D.
54 static CATransform3D GetCATransform3DFromDlMatrix(const DlMatrix& matrix) {
55  CATransform3D transform = CATransform3DIdentity;
56  transform.m11 = matrix.m[0];
57  transform.m12 = matrix.m[1];
58  transform.m13 = matrix.m[2];
59  transform.m14 = matrix.m[3];
60 
61  transform.m21 = matrix.m[4];
62  transform.m22 = matrix.m[5];
63  transform.m23 = matrix.m[6];
64  transform.m24 = matrix.m[7];
65 
66  transform.m31 = matrix.m[8];
67  transform.m32 = matrix.m[9];
68  transform.m33 = matrix.m[10];
69  transform.m34 = matrix.m[11];
70 
71  transform.m41 = matrix.m[12];
72  transform.m42 = matrix.m[13];
73  transform.m43 = matrix.m[14];
74  transform.m44 = matrix.m[15];
75  return transform;
76 }
77 
78 // Reset the anchor of `layer` to match the transform operation from flow.
79 //
80 // The position of the `layer` should be unchanged after resetting the anchor.
81 static void ResetAnchor(CALayer* layer) {
82  // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz.
83  layer.anchorPoint = CGPointZero;
84  layer.position = CGPointZero;
85 }
86 
87 static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) {
88  return CGRectMake(clipDlRect.GetLeft(), //
89  clipDlRect.GetTop(), //
90  clipDlRect.GetWidth(), //
91  clipDlRect.GetHeight());
92 }
93 
94 static bool HasNonRectClipForUnderlayCutout(const flutter::EmbeddedViewParams& params) {
95  auto iter = params.mutatorsStack().Begin();
96  while (iter != params.mutatorsStack().End()) {
97  switch ((*iter)->GetType()) {
98  case flutter::MutatorType::kClipRRect:
99  case flutter::MutatorType::kClipRSE:
100  case flutter::MutatorType::kClipPath:
101  return true;
102  default:
103  break;
104  }
105  ++iter;
106  }
107  return false;
108 }
109 
110 // Overlay canvas needs to be clipped to the shape of platform view to ensure
111 // underlay shows up correctly, so that when there's backdrop filter, the region outside of platform
112 // view's shape is blurred. See: https://github.com/flutter/flutter/issues/150660
113 static void ApplyNonRectClipToOverlayCanvas(flutter::DlCanvas* overlay_canvas,
114  const flutter::EmbeddedViewParams& params) {
115  flutter::DlMatrix transform;
116  auto iter = params.mutatorsStack().Begin();
117  while (iter != params.mutatorsStack().End()) {
118  switch ((*iter)->GetType()) {
119  case flutter::MutatorType::kTransform:
120  transform = transform * (*iter)->GetMatrix();
121  break;
122  case flutter::MutatorType::kClipRRect: {
123  if (transform.IsIdentity()) {
124  overlay_canvas->ClipRoundRect((*iter)->GetRRect(), flutter::DlClipOp::kIntersect, true);
125  } else {
126  auto path = flutter::DlPath::MakeRoundRect((*iter)->GetRRect());
127  auto transformed_path =
128  flutter::DlPath(path.GetSkPath().makeTransform(flutter::ToSkMatrix(transform)));
129  overlay_canvas->ClipPath(transformed_path, flutter::DlClipOp::kIntersect, true);
130  }
131  break;
132  }
133  case flutter::MutatorType::kClipRSE: {
134  if (transform.IsIdentity()) {
135  overlay_canvas->ClipRoundSuperellipse((*iter)->GetRSE(), flutter::DlClipOp::kIntersect,
136  true);
137  } else {
138  auto path = flutter::DlPath::MakeRoundSuperellipse((*iter)->GetRSE());
139  auto transformed_path =
140  flutter::DlPath(path.GetSkPath().makeTransform(flutter::ToSkMatrix(transform)));
141  overlay_canvas->ClipPath(transformed_path, flutter::DlClipOp::kIntersect, true);
142  }
143  break;
144  }
145  case flutter::MutatorType::kClipPath: {
146  if (transform.IsIdentity()) {
147  overlay_canvas->ClipPath((*iter)->GetPath(), flutter::DlClipOp::kIntersect, true);
148  } else {
149  auto transformed_path = flutter::DlPath(
150  (*iter)->GetPath().GetSkPath().makeTransform(flutter::ToSkMatrix(transform)));
151  overlay_canvas->ClipPath(transformed_path, flutter::DlClipOp::kIntersect, true);
152  }
153  break;
154  }
155  default:
156  break;
157  }
158  ++iter;
159  }
160 }
161 
163 
164 // The pool of reusable view layers. The pool allows to recycle layer in each frame.
165 @property(nonatomic, readonly) flutter::OverlayLayerPool* layerPool;
166 
167 // The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent
168 // operation until the next platform view or the end of the last leaf node in the layer tree.
169 //
170 // The Slices are deleted by the PlatformViewsController.reset().
171 @property(nonatomic, readonly)
172  std::unordered_map<int64_t, std::unique_ptr<flutter::EmbedderViewSlice>>& slices;
173 
174 @property(nonatomic, readonly) FlutterClippingMaskViewPool* maskViewPool;
175 
176 @property(nonatomic, readonly)
177  std::unordered_map<std::string, NSObject<FlutterPlatformViewFactory>*>& factories;
178 
179 // The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view.
180 @property(nonatomic, readonly)
181  std::unordered_map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>&
182  gestureRecognizersBlockingPoliciesByType;
183 
184 /// The size of the current onscreen surface in physical pixels.
185 @property(nonatomic, assign) DlISize frameSize;
186 
187 /// The task runner for posting tasks to the platform thread.
188 @property(nonatomic, readonly) FlutterFMLTaskRunner* platformTaskRunner;
189 
190 /// This data must only be accessed on the platform thread.
191 @property(nonatomic, readonly) std::unordered_map<int64_t, PlatformViewData>& platformViews;
192 
193 /// The composition parameters for each platform view.
194 ///
195 /// This state is only modified on the raster thread.
196 @property(nonatomic, readonly)
197  std::unordered_map<int64_t, flutter::EmbeddedViewParams>& currentCompositionParams;
198 
199 /// Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on
200 /// the next frame.
201 ///
202 /// This state is modified on both the platform and raster thread.
203 @property(nonatomic, readonly) std::unordered_set<int64_t>& viewsToDispose;
204 
205 /// view IDs in composition order.
206 ///
207 /// This state is only modified on the raster thread.
208 @property(nonatomic, readonly) std::vector<int64_t>& compositionOrder;
209 
210 /// platform view IDs visited during layer tree composition.
211 ///
212 /// This state is only modified on the raster thread.
213 @property(nonatomic, readonly) std::vector<int64_t>& visitedPlatformViews;
214 
215 /// Only composite platform views in this set.
216 ///
217 /// This state is only modified on the raster thread.
218 @property(nonatomic, readonly) std::unordered_set<int64_t>& viewsToRecomposite;
219 
220 /// Whether the previous frame had any platform views in active composition order.
221 ///
222 /// This state is tracked so that the first frame after removing the last platform view
223 /// runs through the platform view rendering code path, giving us a chance to remove the
224 /// platform view from the UIView hierarchy.
225 ///
226 /// Only accessed from the raster thread.
227 @property(nonatomic, assign) BOOL hadPlatformViews;
228 
229 /// Whether blurred backdrop filters can be applied.
230 ///
231 /// Defaults to YES, but becomes NO if blurred backdrop filters cannot be applied.
232 @property(nonatomic, assign) BOOL canApplyBlurBackdrop;
233 
234 /// Populate any missing overlay layers.
235 ///
236 /// This requires posting a task to the platform thread and blocking on its completion.
237 - (void)createMissingOverlays:(size_t)requiredOverlayLayers
238  withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext;
239 
240 /// Update the buffers and mutate the platform views in CATransaction on the platform thread.
241 - (void)performSubmit:(const LayersMap&)platformViewLayers
242  currentCompositionParams:
243  (std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams
244  viewsToRecomposite:(const std::unordered_set<int64_t>&)viewsToRecomposite
245  compositionOrder:(const std::vector<int64_t>&)compositionOrder
246  unusedLayers:
247  (const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
248  surfaceFrames:
249  (const std::vector<std::unique_ptr<flutter::SurfaceFrame>>&)surfaceFrames;
250 
251 - (void)onCreate:(FlutterMethodCall*)call result:(FlutterResult)result;
252 - (void)onDispose:(FlutterMethodCall*)call result:(FlutterResult)result;
253 - (void)onAcceptGesture:(FlutterMethodCall*)call result:(FlutterResult)result;
254 - (void)onRejectGesture:(FlutterMethodCall*)call result:(FlutterResult)result;
255 
256 - (void)clipViewSetMaskView:(UIView*)clipView;
257 
258 // Applies the mutators in the mutatorsStack to the UIView chain that was constructed by
259 // `ReconstructClipViewsChain`
260 //
261 // Clips are applied to the `embeddedView`'s super view(|ChildClippingView|) using a
262 // |FlutterClippingMaskView|. Transforms are applied to `embeddedView`
263 //
264 // The `boundingRect` is the final bounding rect of the PlatformView
265 // (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding
266 // rect of the PlatformView, the clip mutator is not applied for performance optimization.
267 //
268 // This method is only called when the `embeddedView` needs to be re-composited at the current
269 // frame. See: `compositeView:withParams:` for details.
270 - (void)applyMutators:(const flutter::MutatorsStack&)mutatorsStack
271  embeddedView:(UIView*)embeddedView
272  boundingRect:(const DlRect&)boundingRect;
273 
274 // Appends the overlay views and platform view and sets their z index based on the composition
275 // order.
276 - (void)bringLayersIntoView:(const LayersMap&)layerMap
277  withCompositionOrder:(const std::vector<int64_t>&)compositionOrder;
278 
279 - (std::shared_ptr<flutter::OverlayLayer>)nextLayerInPool;
280 
281 /// Runs on the platform thread.
282 - (void)createLayerWithIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext
283  pixelFormat:(MTLPixelFormat)pixelFormat;
284 
285 /// Removes overlay views and platform views that aren't needed in the current frame.
286 /// Must run on the platform thread.
287 - (void)removeUnusedLayers:(const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
288  withCompositionOrder:(const std::vector<int64_t>&)compositionOrder;
289 
290 /// Computes and returns all views to be disposed on the platform thread, removes them from
291 /// self.platformViews, self.viewsToRecomposite, and self.currentCompositionParams. Any views that
292 /// still require compositing are not returned, but instead added to `viewsToDelayDispose` for
293 /// disposal on the next call.
294 - (std::vector<UIView*>)computeViewsToDispose;
295 
296 /// Resets the state of the frame.
297 - (void)resetFrameState;
298 @end
299 
300 @implementation FlutterPlatformViewsController {
301  // TODO(cbracken): Replace with Obj-C types and use @property declarations to automatically
302  // synthesize the ivars.
303  //
304  // These ivars are required because we're transitioning the previous C++ implementation to Obj-C.
305  // We require ivars to declare the concrete types and then wrap with @property declarations that
306  // return a reference to the ivar, allowing for use like `self.layerPool` and
307  // `self.slices[viewId] = x`.
308  std::unique_ptr<flutter::OverlayLayerPool> _layerPool;
309  std::unordered_map<int64_t, std::unique_ptr<flutter::EmbedderViewSlice>> _slices;
310  std::unordered_map<std::string, NSObject<FlutterPlatformViewFactory>*> _factories;
311  std::unordered_map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>
314  std::unordered_map<int64_t, PlatformViewData> _platformViews;
315  std::unordered_map<int64_t, flutter::EmbeddedViewParams> _currentCompositionParams;
316  std::unordered_set<int64_t> _viewsToDispose;
317  std::vector<int64_t> _compositionOrder;
318  std::vector<int64_t> _visitedPlatformViews;
319  std::unordered_set<int64_t> _viewsToRecomposite;
320  std::vector<int64_t> _previousCompositionOrder;
321 }
322 
323 - (id)init {
324  if (self = [super init]) {
325  _layerPool = std::make_unique<flutter::OverlayLayerPool>();
326  _maskViewPool =
327  [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity];
328  _hadPlatformViews = NO;
329  _canApplyBlurBackdrop = YES;
330  }
331  return self;
332 }
333 
334 - (FlutterFMLTaskRunner*)taskRunner {
335  return _platformTaskRunner;
336 }
337 
338 - (void)setTaskRunner:(FlutterFMLTaskRunner*)platformTaskRunner {
339  _platformTaskRunner = platformTaskRunner;
340 }
341 
342 - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
343  if ([[call method] isEqualToString:@"create"]) {
344  [self onCreate:call result:result];
345  } else if ([[call method] isEqualToString:@"dispose"]) {
346  [self onDispose:call result:result];
347  } else if ([[call method] isEqualToString:@"acceptGesture"]) {
348  [self onAcceptGesture:call result:result];
349  } else if ([[call method] isEqualToString:@"rejectGesture"]) {
350  [self onRejectGesture:call result:result];
351  } else {
353  }
354 }
355 
356 - (void)onCreate:(FlutterMethodCall*)call result:(FlutterResult)result {
357  NSDictionary<NSString*, id>* args = [call arguments];
358 
359  int64_t viewId = [args[@"id"] longLongValue];
360  NSString* viewTypeString = args[@"viewType"];
361  std::string viewType(viewTypeString.UTF8String);
362 
363  if (self.platformViews.count(viewId) != 0) {
364  result([FlutterError errorWithCode:@"recreating_view"
365  message:@"trying to create an already created view"
366  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
367  return;
368  }
369 
370  NSObject<FlutterPlatformViewFactory>* factory = self.factories[viewType];
371  if (factory == nil) {
372  result([FlutterError
373  errorWithCode:@"unregistered_view_type"
374  message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a "
375  @"PlatformView with an unregistered type: < %@ >",
376  viewTypeString]
377  details:@"If you are the author of the PlatformView, make sure `registerViewFactory` "
378  @"is invoked.\n"
379  @"See: "
380  @"https://docs.flutter.dev/development/platform-integration/"
381  @"platform-views#on-the-platform-side-1 for more details.\n"
382  @"If you are not the author of the PlatformView, make sure to call "
383  @"`GeneratedPluginRegistrant.register`."]);
384  return;
385  }
386 
387  id params = nil;
388  if ([factory respondsToSelector:@selector(createArgsCodec)]) {
389  NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
390  if (codec != nil && args[@"params"] != nil) {
391  FlutterStandardTypedData* paramsData = args[@"params"];
392  params = [codec decode:paramsData.data];
393  }
394  }
395 
396  NSObject<FlutterPlatformView>* embeddedView = [factory createWithFrame:CGRectZero
397  viewIdentifier:viewId
398  arguments:params];
399  UIView* platformView = [embeddedView view];
400  // Set a unique view identifier, so the platform view can be identified in unit tests.
401  platformView.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld]", viewId];
402 
403  NSString* gestureBlockingPolicyValue = args[@"gestureBlockingPolicy"];
405  if ([gestureBlockingPolicyValue isEqualToString:kGestureBlockingPolicyDoNotBlockGesture]) {
407  } else if ([gestureBlockingPolicyValue isEqualToString:kGestureBlockingPolicyEagerValue]) {
409  } else if ([gestureBlockingPolicyValue
411  gestureBlockingPolicy =
413  } else if ([gestureBlockingPolicyValue
415  gestureBlockingPolicy = self.gestureRecognizersBlockingPoliciesByType[viewType];
416  } else {
417  result([FlutterError
418  errorWithCode:@"unknown_gesture_blocking_policy"
419  message:@"Trying to create a platform view with an unknown gesture blocking policy"
420  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
421  return;
422  }
423 
424  FlutterTouchInterceptingView* touchInterceptor =
425  [[FlutterTouchInterceptingView alloc] initWithEmbeddedView:platformView
426  platformViewsController:self
427  gestureRecognizersBlockingPolicy:gestureBlockingPolicy];
428 
429  ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero];
430  [clippingView addSubview:touchInterceptor];
431 
432  self.platformViews.emplace(viewId, PlatformViewData{
433  .view = embeddedView, //
434  .touch_interceptor = touchInterceptor, //
435  .root_view = clippingView //
436  });
437 
438  result(nil);
439 }
440 
441 - (void)onDispose:(FlutterMethodCall*)call result:(FlutterResult)result {
442  NSNumber* arg = [call arguments];
443  int64_t viewId = [arg longLongValue];
444 
445  if (self.platformViews.count(viewId) == 0) {
446  result([FlutterError errorWithCode:@"unknown_view"
447  message:@"trying to dispose an unknown"
448  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
449  return;
450  }
451  // We wait for next submitFrame to dispose views.
452  self.viewsToDispose.insert(viewId);
453  result(nil);
454 }
455 
456 - (void)onAcceptGesture:(FlutterMethodCall*)call result:(FlutterResult)result {
457  NSDictionary<NSString*, id>* args = [call arguments];
458  int64_t viewId = [args[@"id"] longLongValue];
459 
460  if (self.platformViews.count(viewId) == 0) {
461  result([FlutterError errorWithCode:@"unknown_view"
462  message:@"trying to set gesture state for an unknown view"
463  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
464  return;
465  }
466 
467  FlutterTouchInterceptingView* view = self.platformViews[viewId].touch_interceptor;
468  [view releaseGesture];
469 
470  result(nil);
471 }
472 
473 - (void)onRejectGesture:(FlutterMethodCall*)call result:(FlutterResult)result {
474  NSDictionary<NSString*, id>* args = [call arguments];
475  int64_t viewId = [args[@"id"] longLongValue];
476 
477  if (self.platformViews.count(viewId) == 0) {
478  result([FlutterError errorWithCode:@"unknown_view"
479  message:@"trying to set gesture state for an unknown view"
480  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
481  return;
482  }
483 
484  FlutterTouchInterceptingView* view = self.platformViews[viewId].touch_interceptor;
485  [view blockGesture];
486 
487  result(nil);
488 }
489 
490 - (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
491  withId:(NSString*)factoryId
492  gestureRecognizersBlockingPolicy:
493  (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizerBlockingPolicy {
494  std::string idString([factoryId UTF8String]);
495  FML_CHECK(self.factories.count(idString) == 0);
496  self.factories[idString] = factory;
497  self.gestureRecognizersBlockingPoliciesByType[idString] = gestureRecognizerBlockingPolicy;
498 }
499 
500 - (void)beginFrameWithSize:(DlISize)frameSize {
501  [self resetFrameState];
502  self.frameSize = frameSize;
503 }
504 
505 - (void)cancelFrame {
506  [self resetFrameState];
507 }
508 
509 - (flutter::PostPrerollResult)postPrerollActionWithThreadMerger:
510  (const fml::RefPtr<fml::RasterThreadMerger>&)rasterThreadMerger {
511  return flutter::PostPrerollResult::kSuccess;
512 }
513 
514 - (void)endFrameWithResubmit:(BOOL)shouldResubmitFrame
515  threadMerger:(const fml::RefPtr<fml::RasterThreadMerger>&)rasterThreadMerger {
516 }
517 
518 - (void)pushFilterToVisitedPlatformViews:(const std::shared_ptr<flutter::DlImageFilter>&)filter
519  withRect:(const flutter::DlRect&)filterRect {
520  for (int64_t id : self.visitedPlatformViews) {
521  flutter::EmbeddedViewParams params = self.currentCompositionParams[id];
522  params.PushImageFilter(filter, filterRect);
523  self.currentCompositionParams[id] = params;
524  }
525 }
526 
527 - (void)prerollCompositeEmbeddedView:(int64_t)viewId
528  withParams:(std::unique_ptr<flutter::EmbeddedViewParams>)params {
529  DlRect viewBounds = DlRect::MakeSize(self.frameSize);
530  std::unique_ptr<flutter::EmbedderViewSlice> view;
531  view = std::make_unique<flutter::DisplayListEmbedderViewSlice>(viewBounds);
532  self.slices.insert_or_assign(viewId, std::move(view));
533 
534  self.compositionOrder.push_back(viewId);
535 
536  if (self.currentCompositionParams.count(viewId) == 1 &&
537  self.currentCompositionParams[viewId] == *params.get()) {
538  // Do nothing if the params didn't change.
539  return;
540  }
541  self.currentCompositionParams[viewId] = flutter::EmbeddedViewParams(*params.get());
542  self.viewsToRecomposite.insert(viewId);
543 }
544 
545 - (size_t)embeddedViewCount {
546  return self.compositionOrder.size();
547 }
548 
549 - (UIView*)platformViewForId:(int64_t)viewId {
550  return [self flutterTouchInterceptingViewForId:viewId].embeddedView;
551 }
552 
553 - (FlutterTouchInterceptingView*)flutterTouchInterceptingViewForId:(int64_t)viewId {
554  if (self.platformViews.empty()) {
555  return nil;
556  }
557  return self.platformViews[viewId].touch_interceptor;
558 }
559 
560 - (long)firstResponderPlatformViewId {
561  for (auto const& [id, platformViewData] : self.platformViews) {
562  UIView* rootView = platformViewData.root_view;
563  if (rootView.flt_hasFirstResponderInViewHierarchySubtree) {
564  return id;
565  }
566  }
567  return -1;
568 }
569 
570 - (void)clipViewSetMaskView:(UIView*)clipView {
571  FML_DCHECK([[NSThread currentThread] isMainThread]);
572  if (clipView.maskView) {
573  return;
574  }
575  CGRect frame =
576  CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
577  CGRectGetWidth(self.flutterView.bounds), CGRectGetHeight(self.flutterView.bounds));
578  clipView.maskView = [self.maskViewPool getMaskViewWithFrame:frame];
579 }
580 
581 - (void)applyMutators:(const flutter::MutatorsStack&)mutatorsStack
582  embeddedView:(UIView*)embeddedView
583  boundingRect:(const DlRect&)boundingRect {
584  if (self.flutterView == nil) {
585  return;
586  }
587 
588  ResetAnchor(embeddedView.layer);
589  ChildClippingView* clipView = (ChildClippingView*)embeddedView.superview;
590 
591  DlMatrix transformMatrix;
592  NSMutableArray* blurFilters = [[NSMutableArray alloc] init];
593  NSMutableArray<PendingRRectClip*>* pendingClipRRects = [[NSMutableArray alloc] init];
594 
595  FML_DCHECK(!clipView.maskView ||
596  [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
597  if (clipView.maskView) {
598  [self.maskViewPool insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
599  clipView.maskView = nil;
600  }
601  CGFloat screenScale = [UIScreen mainScreen].scale;
602  auto iter = mutatorsStack.Begin();
603  while (iter != mutatorsStack.End()) {
604  switch ((*iter)->GetType()) {
605  case flutter::MutatorType::kTransform: {
606  transformMatrix = transformMatrix * (*iter)->GetMatrix();
607  break;
608  }
609  case flutter::MutatorType::kClipRect: {
610  if (flutter::DisplayListMatrixClipState::TransformedRectCoversBounds(
611  (*iter)->GetRect(), transformMatrix, boundingRect)) {
612  break;
613  }
614  [self clipViewSetMaskView:clipView];
615  [(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
616  matrix:transformMatrix];
617  break;
618  }
619  case flutter::MutatorType::kClipRRect: {
620  if (flutter::DisplayListMatrixClipState::TransformedRRectCoversBounds(
621  (*iter)->GetRRect(), transformMatrix, boundingRect)) {
622  break;
623  }
624  [self clipViewSetMaskView:clipView];
625  [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
626  matrix:transformMatrix];
627  break;
628  }
629  case flutter::MutatorType::kClipRSE: {
630  if (flutter::DisplayListMatrixClipState::TransformedRoundSuperellipseCoversBounds(
631  (*iter)->GetRSE(), transformMatrix, boundingRect)) {
632  break;
633  }
634  [self clipViewSetMaskView:clipView];
635  [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRSEApproximation()
636  matrix:transformMatrix];
637  break;
638  }
639  case flutter::MutatorType::kClipPath: {
640  // TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
641  // rect. See `ClipRRectContainsPlatformViewBoundingRect`.
642  // https://github.com/flutter/flutter/issues/118650
643  [self clipViewSetMaskView:clipView];
644  [(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
645  matrix:transformMatrix];
646  break;
647  }
648  case flutter::MutatorType::kOpacity:
649  embeddedView.alpha = (*iter)->GetAlphaFloat() * embeddedView.alpha;
650  break;
651  case flutter::MutatorType::kBackdropFilter: {
652  // Only support DlBlurImageFilter for BackdropFilter.
653  if (!self.canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) {
654  break;
655  }
656  CGRect filterRect = GetCGRectFromDlRect((*iter)->GetFilterMutation().GetFilterRect());
657  // `filterRect` is in global coordinates. We need to convert to local space.
658  filterRect = CGRectApplyAffineTransform(
659  filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale));
660  // `filterRect` reprents the rect that should be filtered inside the `_flutterView`.
661  // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
662  // filtered.
663  if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) {
664  break;
665  }
666  CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
667  CGRect frameInClipView = [self.flutterView convertRect:intersection toView:clipView];
668  // sigma_x is arbitrarily chosen as the radius value because Quartz sets
669  // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
670  // is not supported in Quartz's gaussianBlur CAFilter, so it is not used
671  // to blur the PlatformView.
672  CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
673  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
674  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
675 
676  // TODO(https://github.com/flutter/flutter/issues/179126)
677  CGFloat cornerRadius = 0.0;
678  BOOL isRoundedSuperellipse = NO;
679  // If there's multiple clips, this uses the innermost to decide if its
680  // rse or not. The assumption being the innermost will be the tightest
681  if ([pendingClipRRects count] > 0) {
682  cornerRadius = pendingClipRRects.lastObject.topLeftRadius;
683  isRoundedSuperellipse = pendingClipRRects.lastObject.isRoundedSuperellipse;
684  [pendingClipRRects removeAllObjects];
685  }
686  visualEffectView.layer.cornerRadius = cornerRadius;
687  visualEffectView.layer.cornerCurve =
688  isRoundedSuperellipse ? kCACornerCurveContinuous : kCACornerCurveCircular;
689  visualEffectView.clipsToBounds = YES;
690 
691  PlatformViewFilter* filter = [[PlatformViewFilter alloc] initWithFrame:frameInClipView
692  blurRadius:blurRadius
693  cornerRadius:cornerRadius
694  isRoundedSuperellipse:isRoundedSuperellipse
695  visualEffectView:visualEffectView];
696  if (!filter) {
697  self.canApplyBlurBackdrop = NO;
698  } else {
699  [blurFilters addObject:filter];
700  }
701  break;
702  }
703  case flutter::MutatorType::kBackdropClipRect: {
704  // The frame already handles cropping into the rect so this can
705  // no-op
706  break;
707  }
708  case flutter::MutatorType::kBackdropClipRRect: {
709  PendingRRectClip* clip = [[PendingRRectClip alloc] init];
710  DlRoundRect rrect = (*iter)->GetBackdropClipRRect().rrect;
711 
712  clip.rect = boundingRect;
713  impeller::RoundingRadii radii = rrect.GetRadii();
714  clip.topLeftRadius = radii.top_left.width;
715  clip.topRightRadius = radii.top_right.width;
716  clip.bottomLeftRadius = radii.bottom_left.width;
717  clip.bottomRightRadius = radii.bottom_right.width;
718  [pendingClipRRects addObject:clip];
719  break;
720  }
721  case flutter::MutatorType::kBackdropClipRSuperellipse: {
722  PendingRRectClip* clip = [[PendingRRectClip alloc] init];
723  flutter::DlRoundSuperellipse rse = (*iter)->GetBackdropClipRSuperellipse().rse;
724 
725  clip.rect = boundingRect;
726  impeller::RoundingRadii radii = rse.GetRadii();
727  clip.topLeftRadius = radii.top_left.width;
728  clip.topRightRadius = radii.top_right.width;
729  clip.bottomLeftRadius = radii.bottom_left.width;
730  clip.bottomRightRadius = radii.bottom_right.width;
731  clip.isRoundedSuperellipse = YES;
732  [pendingClipRRects addObject:clip];
733  break;
734  }
735  case flutter::MutatorType::kBackdropClipPath: {
736  // TODO(https://github.com/flutter/flutter/issues/179127)
737  break;
738  }
739  }
740  ++iter;
741  }
742 
743  if (self.canApplyBlurBackdrop) {
744  [clipView applyBlurBackdropFilters:blurFilters];
745  }
746 
747  // The UIKit frame is set based on the logical resolution (points) instead of physical.
748  // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
749  // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
750  // 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix
751  // down to the logical resoltion before applying it to the layer of PlatformView.
752  flutter::DlScalar pointScale = 1.0 / screenScale;
753  transformMatrix = DlMatrix::MakeScale({pointScale, pointScale, 1}) * transformMatrix;
754 
755  // Reverse the offset of the clipView.
756  // The clipView's frame includes the final translate of the final transform matrix.
757  // Thus, this translate needs to be reversed so the platform view can layout at the correct
758  // offset.
759  //
760  // Note that the transforms are not applied to the clipping paths because clipping paths happen on
761  // the mask view, whose origin is always (0,0) to the _flutterView.
762  impeller::Vector3 origin = impeller::Vector3(clipView.frame.origin.x, clipView.frame.origin.y);
763  transformMatrix = DlMatrix::MakeTranslation(-origin) * transformMatrix;
764 
765  embeddedView.layer.transform = GetCATransform3DFromDlMatrix(transformMatrix);
766 }
767 
768 - (void)compositeView:(int64_t)viewId withParams:(const flutter::EmbeddedViewParams&)params {
769  // TODO(https://github.com/flutter/flutter/issues/109700)
770  CGRect frame = CGRectMake(0, 0, params.sizePoints().width, params.sizePoints().height);
771  FlutterTouchInterceptingView* touchInterceptor = self.platformViews[viewId].touch_interceptor;
772  touchInterceptor.layer.transform = CATransform3DIdentity;
773  touchInterceptor.frame = frame;
774  touchInterceptor.alpha = 1;
775 
776  const flutter::MutatorsStack& mutatorStack = params.mutatorsStack();
777  UIView* clippingView = self.platformViews[viewId].root_view;
778  // The frame of the clipping view should be the final bounding rect.
779  // Because the translate matrix in the Mutator Stack also includes the offset,
780  // when we apply the transforms matrix in |applyMutators:embeddedView:boundingRect|, we need
781  // to remember to do a reverse translate.
782  const DlRect& rect = params.finalBoundingRect();
783  CGFloat screenScale = [UIScreen mainScreen].scale;
784  clippingView.frame = CGRectMake(rect.GetX() / screenScale, rect.GetY() / screenScale,
785  rect.GetWidth() / screenScale, rect.GetHeight() / screenScale);
786  [self applyMutators:mutatorStack embeddedView:touchInterceptor boundingRect:rect];
787 }
788 
789 - (flutter::DlCanvas*)compositeEmbeddedViewWithId:(int64_t)viewId {
790  FML_DCHECK(self.slices.find(viewId) != self.slices.end());
791  return self.slices[viewId]->canvas();
792 }
793 
794 - (void)reset {
795  // Reset will only be called from the raster thread or a merged raster/platform thread.
796  // _platformViews must only be modified on the platform thread, and any operations that
797  // read or modify platform views should occur there.
798  std::vector<int64_t> compositionOrder = self.compositionOrder;
799  [self.taskRunner runNowOrPostTask:^{
800  for (int64_t viewId : compositionOrder) {
801  [self.platformViews[viewId].root_view removeFromSuperview];
802  }
803  self.platformViews.clear();
804  _previousCompositionOrder.clear();
805  }];
806 
807  self.compositionOrder.clear();
808  self.slices.clear();
809  self.currentCompositionParams.clear();
810  self.viewsToRecomposite.clear();
811  self.layerPool->RecycleLayers();
812  self.visitedPlatformViews.clear();
813 }
814 
815 - (BOOL)submitFrame:(std::unique_ptr<flutter::SurfaceFrame>)background_frame
816  withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext {
817  TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame");
818 
819  // No platform views to render.
820  if (self.flutterView == nil || (self.compositionOrder.empty() && !self.hadPlatformViews)) {
821  // No platform views to render but the FlutterView may need to be resized.
822  __weak FlutterPlatformViewsController* weakSelf = self;
823  if (self.flutterView != nil) {
824  // Pass frameSize by value since self.frameSize is mutated both here (on the platform
825  // thread) and in beginFrameWithSize: (on the raster thread).
826  const flutter::DlISize frameSize = self.frameSize;
827  [self.taskRunner runNowOrPostTask:^{
828  FlutterPlatformViewsController* strongSelf = weakSelf;
829  if (!strongSelf) {
830  return;
831  }
832  [strongSelf performResize:frameSize];
833  }];
834  }
835 
836  self.hadPlatformViews = NO;
837  return background_frame->Submit();
838  }
839  self.hadPlatformViews = !self.compositionOrder.empty();
840 
841  bool didEncode = true;
842  LayersMap platformViewLayers;
843  std::vector<std::unique_ptr<flutter::SurfaceFrame>> surfaceFrames;
844  surfaceFrames.reserve(self.compositionOrder.size());
845  std::unordered_map<int64_t, DlRect> viewRects;
846  std::unordered_set<int64_t> viewsWithUnderlayPreserved;
847 
848  for (int64_t viewId : self.compositionOrder) {
849  const flutter::EmbeddedViewParams& params = self.currentCompositionParams[viewId];
850  viewRects[viewId] = params.finalBoundingRect();
851  if (HasNonRectClipForUnderlayCutout(params)) {
852  viewsWithUnderlayPreserved.insert(viewId);
853  }
854  }
855 
856  std::unordered_map<int64_t, DlRect> overlayLayers =
857  SliceViews(background_frame->Canvas(), self.compositionOrder, self.slices, viewRects,
858  viewsWithUnderlayPreserved);
859 
860  size_t requiredOverlayLayers = 0;
861  for (int64_t viewId : self.compositionOrder) {
862  std::unordered_map<int64_t, DlRect>::const_iterator overlay = overlayLayers.find(viewId);
863  if (overlay == overlayLayers.end()) {
864  continue;
865  }
866  requiredOverlayLayers++;
867  }
868 
869  // If there are not sufficient overlay layers, we must construct them on the platform
870  // thread, at least until we've refactored iOS surface creation to use IOSurfaces
871  // instead of CALayers.
872  [self createMissingOverlays:requiredOverlayLayers withIosContext:iosContext];
873 
874  int64_t overlayId = 0;
875  for (int64_t viewId : self.compositionOrder) {
876  std::unordered_map<int64_t, DlRect>::const_iterator overlay = overlayLayers.find(viewId);
877  if (overlay == overlayLayers.end()) {
878  continue;
879  }
880  std::shared_ptr<flutter::OverlayLayer> layer = self.nextLayerInPool;
881  if (!layer) {
882  continue;
883  }
884 
885  std::unique_ptr<flutter::SurfaceFrame> frame = layer->surface->AcquireFrame(self.frameSize);
886  // If frame is null, AcquireFrame already printed out an error message.
887  if (!frame) {
888  continue;
889  }
890  flutter::DlCanvas* overlayCanvas = frame->Canvas();
891  int restoreCount = overlayCanvas->GetSaveCount();
892  overlayCanvas->Save();
893  overlayCanvas->ClipRect(overlay->second);
894  if (viewsWithUnderlayPreserved.find(viewId) != viewsWithUnderlayPreserved.end()) {
895  ApplyNonRectClipToOverlayCanvas(overlayCanvas, self.currentCompositionParams[viewId]);
896  }
897  overlayCanvas->Clear(flutter::DlColor::kTransparent());
898  self.slices[viewId]->render_into(overlayCanvas);
899  overlayCanvas->RestoreToCount(restoreCount);
900 
901  // This flutter view is never the last in a frame, since we always submit the
902  // underlay view last.
903  frame->set_submit_info({.frame_boundary = false, .present_with_transaction = true});
904  layer->did_submit_last_frame = frame->Encode();
905 
906  didEncode &= layer->did_submit_last_frame;
907  platformViewLayers[viewId] = LayerData{
908  .rect = overlay->second, //
909  .view_id = viewId, //
910  .overlay_id = overlayId, //
911  .layer = layer //
912  };
913  surfaceFrames.push_back(std::move(frame));
914  overlayId++;
915  }
916 
917  auto previousSubmitInfo = background_frame->submit_info();
918  background_frame->set_submit_info({
919  .frame_damage = previousSubmitInfo.frame_damage,
920  .buffer_damage = previousSubmitInfo.buffer_damage,
921  .present_with_transaction = true,
922  });
923  background_frame->Encode();
924  surfaceFrames.push_back(std::move(background_frame));
925 
926  // Mark all layers as available, so they can be used in the next frame.
927  std::vector<std::shared_ptr<flutter::OverlayLayer>> unusedLayers =
928  self.layerPool->RemoveUnusedLayers();
929  self.layerPool->RecycleLayers();
930  auto task = fml::MakeCopyable([self, //
931  platformViewLayers = std::move(platformViewLayers), //
932  currentCompositionParams = self.currentCompositionParams, //
933  viewsToRecomposite = self.viewsToRecomposite, //
934  compositionOrder = self.compositionOrder, //
935  unusedLayers = std::move(unusedLayers), //
936  surfaceFrames = std::move(surfaceFrames)]() mutable {
937  [self performSubmit:platformViewLayers
938  currentCompositionParams:currentCompositionParams
939  viewsToRecomposite:viewsToRecomposite
940  compositionOrder:compositionOrder
941  unusedLayers:unusedLayers
942  surfaceFrames:surfaceFrames];
943  });
944 
945  [self.taskRunner runNowOrPostTask:^{
946  task();
947  }];
948  return didEncode;
949 }
950 
951 - (void)createMissingOverlays:(size_t)requiredOverlayLayers
952  withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext {
953  TRACE_EVENT0("flutter", "PlatformViewsController::CreateMissingLayers");
954 
955  if (requiredOverlayLayers <= self.layerPool->size()) {
956  return;
957  }
958  auto missingLayerCount = requiredOverlayLayers - self.layerPool->size();
959 
960  // If the raster thread isn't merged, create layers on the platform thread and block until
961  // complete. The self-capture here is fine since this is effectively synchronous (we block on the
962  // latch right below).
963  auto latch = std::make_shared<fml::CountDownLatch>(1u);
964  [self.taskRunner runNowOrPostTask:^{
965  for (auto i = 0u; i < missingLayerCount; i++) {
966  [self createLayerWithIosContext:iosContext
967  pixelFormat:((FlutterView*)self.flutterView).pixelFormat];
968  }
969  latch->CountDown();
970  }];
971  if (![[NSThread currentThread] isMainThread]) {
972  latch->Wait();
973  }
974 }
975 
976 - (void)performResize:(const flutter::DlISize&)frameSize {
977  TRACE_EVENT0("flutter", "PlatformViewsController::PerformResize");
978  FML_DCHECK([[NSThread currentThread] isMainThread]);
979 
980  if (self.flutterView != nil) {
981  [(FlutterView*)self.flutterView
982  setIntrinsicContentSize:CGSizeMake(frameSize.width, frameSize.height)];
983  }
984 }
985 
986 - (void)performSubmit:(const LayersMap&)platformViewLayers
987  currentCompositionParams:
988  (std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams
989  viewsToRecomposite:(const std::unordered_set<int64_t>&)viewsToRecomposite
990  compositionOrder:(const std::vector<int64_t>&)compositionOrder
991  unusedLayers:
992  (const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
993  surfaceFrames:
994  (const std::vector<std::unique_ptr<flutter::SurfaceFrame>>&)surfaceFrames {
995  TRACE_EVENT0("flutter", "PlatformViewsController::PerformSubmit");
996  FML_DCHECK([[NSThread currentThread] isMainThread]);
997 
998  [CATransaction begin];
999 
1000  // Configure Flutter overlay views.
1001  for (const auto& [viewId, layerData] : platformViewLayers) {
1002  layerData.layer->UpdateViewState(self.flutterView, //
1003  layerData.rect, //
1004  layerData.view_id, //
1005  layerData.overlay_id //
1006  );
1007  }
1008 
1009  // Dispose unused Flutter Views.
1010  for (auto& view : [self computeViewsToDispose]) {
1011  [view removeFromSuperview];
1012  }
1013 
1014  // Composite Platform Views.
1015  for (int64_t viewId : viewsToRecomposite) {
1016  [self compositeView:viewId withParams:currentCompositionParams[viewId]];
1017  }
1018 
1019  // Present callbacks.
1020  for (const auto& frame : surfaceFrames) {
1021  frame->Submit();
1022  }
1023 
1024  // If a layer was allocated in the previous frame, but it's not used in the current frame,
1025  // then it can be removed from the scene.
1026  [self removeUnusedLayers:unusedLayers withCompositionOrder:compositionOrder];
1027 
1028  // Organize the layers by their z indexes.
1029  [self bringLayersIntoView:platformViewLayers withCompositionOrder:compositionOrder];
1030 
1031  [CATransaction commit];
1032 }
1033 
1034 - (void)bringLayersIntoView:(const LayersMap&)layerMap
1035  withCompositionOrder:(const std::vector<int64_t>&)compositionOrder {
1036  FML_DCHECK(self.flutterView);
1037  UIView* flutterView = self.flutterView;
1038 
1039  _previousCompositionOrder.clear();
1040  NSMutableArray* desiredPlatformSubviews = [NSMutableArray array];
1041  for (int64_t platformViewId : compositionOrder) {
1042  _previousCompositionOrder.push_back(platformViewId);
1043  UIView* platformViewRoot = self.platformViews[platformViewId].root_view;
1044  if (platformViewRoot != nil) {
1045  [desiredPlatformSubviews addObject:platformViewRoot];
1046  }
1047 
1048  auto maybeLayerData = layerMap.find(platformViewId);
1049  if (maybeLayerData != layerMap.end()) {
1050  auto view = maybeLayerData->second.layer->overlay_view_wrapper;
1051  if (view != nil) {
1052  [desiredPlatformSubviews addObject:view];
1053  }
1054  }
1055  }
1056 
1057  NSSet* desiredPlatformSubviewsSet = [NSSet setWithArray:desiredPlatformSubviews];
1058  NSArray* existingPlatformSubviews = [flutterView.subviews
1059  filteredArrayUsingPredicate:[NSPredicate
1060  predicateWithBlock:^BOOL(id object, NSDictionary* bindings) {
1061  return [desiredPlatformSubviewsSet containsObject:object];
1062  }]];
1063 
1064  // Manipulate view hierarchy only if needed, to address a performance issue where
1065  // this method is called even when view hierarchy stays the same.
1066  // See: https://github.com/flutter/flutter/issues/121833
1067  // TODO(hellohuanlin): investigate if it is possible to skip unnecessary bringLayersIntoView.
1068  if (![desiredPlatformSubviews isEqualToArray:existingPlatformSubviews]) {
1069  for (UIView* subview in desiredPlatformSubviews) {
1070  // `addSubview` will automatically reorder subview if it is already added.
1071  [flutterView addSubview:subview];
1072  }
1073  }
1074 }
1075 
1076 - (std::shared_ptr<flutter::OverlayLayer>)nextLayerInPool {
1077  return self.layerPool->GetNextLayer();
1078 }
1079 
1080 - (void)createLayerWithIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext
1081  pixelFormat:(MTLPixelFormat)pixelFormat {
1082  self.layerPool->CreateLayer(iosContext, pixelFormat);
1083 }
1084 
1085 - (void)removeUnusedLayers:(const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
1086  withCompositionOrder:(const std::vector<int64_t>&)compositionOrder {
1087  for (const std::shared_ptr<flutter::OverlayLayer>& layer : unusedLayers) {
1088  [layer->overlay_view_wrapper removeFromSuperview];
1089  }
1090 
1091  std::unordered_set<int64_t> compositionOrderSet;
1092  for (int64_t viewId : compositionOrder) {
1093  compositionOrderSet.insert(viewId);
1094  }
1095  // Remove unused platform views.
1096  for (int64_t viewId : _previousCompositionOrder) {
1097  if (compositionOrderSet.find(viewId) == compositionOrderSet.end()) {
1098  UIView* platformViewRoot = self.platformViews[viewId].root_view;
1099  [platformViewRoot removeFromSuperview];
1100  }
1101  }
1102 }
1103 
1104 - (std::vector<UIView*>)computeViewsToDispose {
1105  std::vector<UIView*> views;
1106  if (self.viewsToDispose.empty()) {
1107  return views;
1108  }
1109 
1110  std::unordered_set<int64_t> viewsToComposite(self.compositionOrder.begin(),
1111  self.compositionOrder.end());
1112  std::unordered_set<int64_t> viewsToDelayDispose;
1113  for (int64_t viewId : self.viewsToDispose) {
1114  if (viewsToComposite.count(viewId)) {
1115  viewsToDelayDispose.insert(viewId);
1116  continue;
1117  }
1118  UIView* rootView = self.platformViews[viewId].root_view;
1119  views.push_back(rootView);
1120  self.currentCompositionParams.erase(viewId);
1121  self.viewsToRecomposite.erase(viewId);
1122  self.platformViews.erase(viewId);
1123  }
1124  self.viewsToDispose = std::move(viewsToDelayDispose);
1125  return views;
1126 }
1127 
1128 - (void)resetFrameState {
1129  self.slices.clear();
1130  self.compositionOrder.clear();
1131  self.visitedPlatformViews.clear();
1132 }
1133 
1134 - (void)pushVisitedPlatformViewId:(int64_t)viewId {
1135  self.visitedPlatformViews.push_back(viewId);
1136 }
1137 
1138 - (void)pushClipRectToVisitedPlatformViews:(const flutter::DlRect&)clipRect {
1139  for (int64_t id : self.visitedPlatformViews) {
1140  flutter::EmbeddedViewParams params = self.currentCompositionParams[id];
1141  params.PushPlatformViewClipRect(clipRect);
1142  self.currentCompositionParams[id] = params;
1143  }
1144 }
1145 
1146 - (void)pushClipRRectToVisitedPlatformViews:(const flutter::DlRoundRect&)clipRRect {
1147  for (int64_t id : self.visitedPlatformViews) {
1148  flutter::EmbeddedViewParams params = self.currentCompositionParams[id];
1149  params.PushPlatformViewClipRRect(clipRRect);
1150  self.currentCompositionParams[id] = params;
1151  }
1152 }
1153 
1154 - (void)pushClipRSuperellipseToVisitedPlatformViews:(const flutter::DlRoundSuperellipse&)clipRse {
1155  for (int64_t id : self.visitedPlatformViews) {
1156  flutter::EmbeddedViewParams params = self.currentCompositionParams[id];
1157  params.PushPlatformViewClipRSuperellipse(clipRse);
1158  self.currentCompositionParams[id] = params;
1159  }
1160 }
1161 
1162 - (void)pushClipPathToVisitedPlatformViews:(const flutter::DlPath&)clipPath {
1163  for (int64_t id : self.visitedPlatformViews) {
1164  flutter::EmbeddedViewParams params = self.currentCompositionParams[id];
1165  params.PushPlatformViewClipPath(clipPath);
1166  self.currentCompositionParams[id] = params;
1167  }
1168 }
1169 
1170 - (const flutter::EmbeddedViewParams&)compositionParamsForView:(int64_t)viewId {
1171  return self.currentCompositionParams.find(viewId)->second;
1172 }
1173 
1174 #pragma mark - Properties
1175 
1176 - (flutter::OverlayLayerPool*)layerPool {
1177  return _layerPool.get();
1178 }
1179 
1180 - (std::unordered_map<int64_t, std::unique_ptr<flutter::EmbedderViewSlice>>&)slices {
1181  return _slices;
1182 }
1183 
1184 - (std::unordered_map<std::string, NSObject<FlutterPlatformViewFactory>*>&)factories {
1185  return _factories;
1186 }
1187 
1188 - (std::unordered_map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>&)
1189  gestureRecognizersBlockingPoliciesByType {
1191 }
1192 
1193 - (std::unordered_map<int64_t, PlatformViewData>&)platformViews {
1194  return _platformViews;
1195 }
1196 
1197 - (std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams {
1199 }
1200 
1201 - (std::unordered_set<int64_t>&)viewsToDispose {
1202  return _viewsToDispose;
1203 }
1204 
1205 - (std::vector<int64_t>&)compositionOrder {
1206  return _compositionOrder;
1207 }
1208 
1209 - (std::vector<int64_t>&)visitedPlatformViews {
1210  return _visitedPlatformViews;
1211 }
1212 
1213 - (std::unordered_set<int64_t>&)viewsToRecomposite {
1214  return _viewsToRecomposite;
1215 }
1216 
1217 - (NSArray<NSNumber*>*)previousCompositionOrder {
1218  // TODO(cbracken): Migrate to Obj-C types. https://github.com/flutter/flutter/issues/185139
1219  NSMutableArray* array = [NSMutableArray arrayWithCapacity:_previousCompositionOrder.size()];
1220  for (int64_t viewId : _previousCompositionOrder) {
1221  [array addObject:@(viewId)];
1222  }
1223  return array;
1224 }
1225 
1226 @end
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
static bool HasNonRectClipForUnderlayCutout(const flutter::EmbeddedViewParams &params)
std::unordered_map< int64_t, LayerData > LayersMap
std::vector< int64_t > _compositionOrder
std::unordered_set< int64_t > _viewsToRecomposite
std::unordered_map< std::string, NSObject< FlutterPlatformViewFactory > * > _factories
static NSString *const kGestureBlockingPolicyFallbackToPluginDefault
static NSString *const kGestureBlockingPolicyWaitUntilTouchesEndedValue
std::unordered_map< std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy > _gestureRecognizersBlockingPoliciesByType
std::vector< int64_t > _previousCompositionOrder
static void ApplyNonRectClipToOverlayCanvas(flutter::DlCanvas *overlay_canvas, const flutter::EmbeddedViewParams &params)
FlutterFMLTaskRunner * _platformTaskRunner
static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity
std::unordered_map< int64_t, PlatformViewData > _platformViews
std::unordered_map< int64_t, std::unique_ptr< flutter::EmbedderViewSlice > > _slices
std::vector< int64_t > _visitedPlatformViews
std::unordered_map< int64_t, flutter::EmbeddedViewParams > _currentCompositionParams
static void ResetAnchor(CALayer *layer)
static NSString *const kGestureBlockingPolicyDoNotBlockGesture
static CATransform3D GetCATransform3DFromDlMatrix(const DlMatrix &matrix)
static NSString *const kGestureBlockingPolicyEagerValue
static CGRect GetCGRectFromDlRect(const DlRect &clipDlRect)
std::unordered_set< int64_t > _viewsToDispose
FlutterPlatformViewGestureRecognizersBlockingPolicy
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
@ FlutterPlatformViewGestureRecognizersBlockingPolicyDoNotBlockGesture
void applyBlurBackdropFilters:(NSArray< PlatformViewFilter * > *filters)
Storage for Overlay layers across frames.
std::shared_ptr< flutter::OverlayLayer > layer
FlutterTouchInterceptingView * touch_interceptor
NSObject< FlutterPlatformView > * view