Flutter Engine
FlutterPlatformViews.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 
5 #import <UIKit/UIGestureRecognizerSubclass.h>
6 
7 #include <list>
8 #include <map>
9 #include <memory>
10 #include <string>
11 
12 #include "flutter/flow/rtree.h"
13 #include "flutter/fml/platform/darwin/scoped_nsobject.h"
14 #include "flutter/shell/common/persistent_cache.h"
15 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
16 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
17 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
18 #import "flutter/shell/platform/darwin/ios/ios_surface.h"
19 #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h"
20 
21 namespace flutter {
22 
23 std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewLayerPool::GetLayer(
24  GrDirectContext* gr_context,
25  std::shared_ptr<IOSContext> ios_context) {
26  if (available_layer_index_ >= layers_.size()) {
27  std::shared_ptr<FlutterPlatformViewLayer> layer;
29  fml::scoped_nsobject<FlutterOverlayView> overlay_view_wrapper;
30 
31  if (!gr_context) {
32  overlay_view.reset([[FlutterOverlayView alloc] init]);
33  overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]);
34 
35  std::unique_ptr<IOSSurface> ios_surface =
36  [overlay_view.get() createSurface:std::move(ios_context)];
37  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface();
38 
39  layer = std::make_shared<FlutterPlatformViewLayer>(
40  std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
41  std::move(surface));
42  } else {
43  CGFloat screenScale = [UIScreen mainScreen].scale;
44  overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]);
45  overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]);
46 
47  std::unique_ptr<IOSSurface> ios_surface =
48  [overlay_view.get() createSurface:std::move(ios_context)];
49  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
50 
51  layer = std::make_shared<FlutterPlatformViewLayer>(
52  std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
53  std::move(surface));
54  layer->gr_context = gr_context;
55  }
56  // The overlay view wrapper masks the overlay view.
57  // This is required to keep the backing surface size unchanged between frames.
58  //
59  // Otherwise, changing the size of the overlay would require a new surface,
60  // which can be very expensive.
61  //
62  // This is the case of an animation in which the overlay size is changing in every frame.
63  //
64  // +------------------------+
65  // | overlay_view |
66  // | +--------------+ | +--------------+
67  // | | wrapper | | == mask => | overlay_view |
68  // | +--------------+ | +--------------+
69  // +------------------------+
70  overlay_view_wrapper.get().clipsToBounds = YES;
71  [overlay_view_wrapper.get() addSubview:overlay_view];
72  layers_.push_back(layer);
73  }
74  std::shared_ptr<FlutterPlatformViewLayer> layer = layers_[available_layer_index_];
75  if (gr_context != layer->gr_context) {
76  layer->gr_context = gr_context;
77  // The overlay already exists, but the GrContext was changed so we need to recreate
78  // the rendering surface with the new GrContext.
79  IOSSurface* ios_surface = layer->ios_surface.get();
80  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
81  layer->surface = std::move(surface);
82  }
83  available_layer_index_++;
84  return layer;
85 }
86 
88  available_layer_index_ = 0;
89 }
90 
91 std::vector<std::shared_ptr<FlutterPlatformViewLayer>>
93  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> results;
94  for (size_t i = available_layer_index_; i < layers_.size(); i++) {
95  results.push_back(layers_[i]);
96  }
97  return results;
98 }
99 
101  flutter_view_.reset([flutter_view retain]);
102 }
103 
105  UIViewController* flutter_view_controller) {
106  flutter_view_controller_.reset([flutter_view_controller retain]);
107 }
108 
110  return flutter_view_controller_.get();
111 }
112 
114  if ([[call method] isEqualToString:@"create"]) {
115  OnCreate(call, result);
116  } else if ([[call method] isEqualToString:@"dispose"]) {
117  OnDispose(call, result);
118  } else if ([[call method] isEqualToString:@"acceptGesture"]) {
119  OnAcceptGesture(call, result);
120  } else if ([[call method] isEqualToString:@"rejectGesture"]) {
121  OnRejectGesture(call, result);
122  } else {
124  }
125 }
126 
127 void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult& result) {
128  NSDictionary<NSString*, id>* args = [call arguments];
129 
130  long viewId = [args[@"id"] longValue];
131  std::string viewType([args[@"viewType"] UTF8String]);
132 
133  if (views_.count(viewId) != 0) {
134  result([FlutterError errorWithCode:@"recreating_view"
135  message:@"trying to create an already created view"
136  details:[NSString stringWithFormat:@"view id: '%ld'", viewId]]);
137  }
138 
139  NSObject<FlutterPlatformViewFactory>* factory = factories_[viewType].get();
140  if (factory == nil) {
141  result([FlutterError errorWithCode:@"unregistered_view_type"
142  message:@"trying to create a view with an unregistered type"
143  details:[NSString stringWithFormat:@"unregistered view type: '%@'",
144  args[@"viewType"]]]);
145  return;
146  }
147 
148  id params = nil;
149  if ([factory respondsToSelector:@selector(createArgsCodec)]) {
150  NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
151  if (codec != nil && args[@"params"] != nil) {
152  FlutterStandardTypedData* paramsData = args[@"params"];
153  params = [codec decode:paramsData.data];
154  }
155  }
156 
157  NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero
158  viewIdentifier:viewId
159  arguments:params];
160  // Set a unique view identifier, so the platform view can be identified in unit tests.
161  [embedded_view view].accessibilityIdentifier =
162  [NSString stringWithFormat:@"platform_view[%ld]", viewId];
163  views_[viewId] = fml::scoped_nsobject<NSObject<FlutterPlatformView>>([embedded_view retain]);
164 
165  FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
166  initWithEmbeddedView:embedded_view.view
167  platformViewsController:GetWeakPtr()
168  gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]]
169  autorelease];
170 
171  touch_interceptors_[viewId] =
172  fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
173 
174  ChildClippingView* clipping_view =
175  [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease];
176  [clipping_view addSubview:touch_interceptor];
177  root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]);
178 
179  result(nil);
180 }
181 
182 void FlutterPlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult& result) {
183  NSNumber* arg = [call arguments];
184  int64_t viewId = [arg longLongValue];
185 
186  if (views_.count(viewId) == 0) {
187  result([FlutterError errorWithCode:@"unknown_view"
188  message:@"trying to dispose an unknown"
189  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
190  return;
191  }
192  // We wait for next submitFrame to dispose views.
193  views_to_dispose_.insert(viewId);
194  result(nil);
195 }
196 
197 void FlutterPlatformViewsController::OnAcceptGesture(FlutterMethodCall* call,
198  FlutterResult& result) {
199  NSDictionary<NSString*, id>* args = [call arguments];
200  int64_t viewId = [args[@"id"] longLongValue];
201 
202  if (views_.count(viewId) == 0) {
203  result([FlutterError errorWithCode:@"unknown_view"
204  message:@"trying to set gesture state for an unknown view"
205  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
206  return;
207  }
208 
209  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
210  [view releaseGesture];
211 
212  result(nil);
213 }
214 
215 void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call,
216  FlutterResult& result) {
217  NSDictionary<NSString*, id>* args = [call arguments];
218  int64_t viewId = [args[@"id"] longLongValue];
219 
220  if (views_.count(viewId) == 0) {
221  result([FlutterError errorWithCode:@"unknown_view"
222  message:@"trying to set gesture state for an unknown view"
223  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
224  return;
225  }
226 
227  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
228  [view blockGesture];
229 
230  result(nil);
231 }
232 
234  NSObject<FlutterPlatformViewFactory>* factory,
235  NSString* factoryId,
236  FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) {
237  std::string idString([factoryId UTF8String]);
238  FML_CHECK(factories_.count(idString) == 0);
239  factories_[idString] =
241  gesture_recognizers_blocking_policies[idString] = gestureRecognizerBlockingPolicy;
242 }
243 
245  frame_size_ = frame_size;
246 }
247 
249  picture_recorders_.clear();
250  composition_order_.clear();
251 }
252 
253 // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
254 // Make this method check if there are pending view operations instead.
255 // Also rename it to `HasPendingViewOperations`.
256 bool FlutterPlatformViewsController::HasPlatformViewThisOrNextFrame() {
257  return composition_order_.size() > 0 || active_composition_order_.size() > 0;
258 }
259 
260 const int FlutterPlatformViewsController::kDefaultMergedLeaseDuration;
261 
263  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
264  // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
265  // Rename `has_platform_view` to `view_mutated` when the above issue is resolved.
266  if (!HasPlatformViewThisOrNextFrame()) {
268  }
269  if (!raster_thread_merger->IsMerged()) {
270  // The raster thread merger may be disabled if the rasterizer is being
271  // created or teared down.
272  //
273  // In such cases, the current frame is dropped, and a new frame is attempted
274  // with the same layer tree.
275  //
276  // Eventually, the frame is submitted once this method returns `kSuccess`.
277  // At that point, the raster tasks are handled on the platform thread.
278  raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
279  CancelFrame();
281  }
282  // If the post preroll action is successful, we will display platform views in the current frame.
283  // In order to sync the rendering of the platform views (quartz) with skia's rendering,
284  // We need to begin an explicit CATransaction. This transaction needs to be submitted
285  // after the current frame is submitted.
286  BeginCATransaction();
287  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
289 }
290 
292  int view_id,
293  std::unique_ptr<EmbeddedViewParams> params) {
294  // All the CATransactions should be committed by the end of the last frame,
295  // so catransaction_added_ must be false.
296  FML_DCHECK(!catransaction_added_);
297  picture_recorders_[view_id] = std::make_unique<SkPictureRecorder>();
298 
299  auto rtree_factory = RTreeFactory();
300  platform_view_rtrees_[view_id] = rtree_factory.getInstance();
301  picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_), &rtree_factory);
302 
303  composition_order_.push_back(view_id);
304 
305  if (current_composition_params_.count(view_id) == 1 &&
306  current_composition_params_[view_id] == *params.get()) {
307  // Do nothing if the params didn't change.
308  return;
309  }
310  current_composition_params_[view_id] = EmbeddedViewParams(*params.get());
311  views_to_recomposite_.insert(view_id);
312 }
313 
314 NSObject<FlutterPlatformView>* FlutterPlatformViewsController::GetPlatformViewByID(int view_id) {
315  if (views_.empty()) {
316  return nil;
317  }
318  return views_[view_id].get();
319 }
320 
322  std::vector<SkCanvas*> canvases;
323  for (size_t i = 0; i < composition_order_.size(); i++) {
324  int64_t view_id = composition_order_[i];
325  canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas());
326  }
327  return canvases;
328 }
329 
330 int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_stack) {
331  std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom();
332  int clipCount = 0;
333  while (iter != mutators_stack.Top()) {
334  if ((*iter)->IsClipType()) {
335  clipCount++;
336  }
337  ++iter;
338  }
339  return clipCount;
340 }
341 
342 void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
343  UIView* embedded_view) {
344  FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
345  ResetAnchor(embedded_view.layer);
346  ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
347 
348  // The UIKit frame is set based on the logical resolution instead of physical.
349  // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
350  // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
351  // 500 points in UIKit. And until this point, we did all the calculation based on the flow
352  // resolution. So we need to scale down to match UIKit's logical resolution.
353  CGFloat screenScale = [UIScreen mainScreen].scale;
354  CATransform3D finalTransform = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
355 
356  // Mask view needs to be full screen because we might draw platform view pixels outside of the
357  // `ChildClippingView`. Since the mask view's frame will be based on the `clipView`'s coordinate
358  // system, we need to convert the flutter_view's frame to the clipView's coordinate system. The
359  // mask view is not displayed on the screen.
360  CGRect maskViewFrame = [flutter_view_ convertRect:flutter_view_.get().frame toView:clipView];
361  FlutterClippingMaskView* maskView =
362  [[[FlutterClippingMaskView alloc] initWithFrame:maskViewFrame] autorelease];
363  auto iter = mutators_stack.Begin();
364  while (iter != mutators_stack.End()) {
365  switch ((*iter)->GetType()) {
366  case transform: {
367  CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
368  finalTransform = CATransform3DConcat(transform, finalTransform);
369  break;
370  }
371  case clip_rect:
372  [maskView clipRect:(*iter)->GetRect() matrix:finalTransform];
373  break;
374  case clip_rrect:
375  [maskView clipRRect:(*iter)->GetRRect() matrix:finalTransform];
376  break;
377  case clip_path:
378  [maskView clipPath:(*iter)->GetPath() matrix:finalTransform];
379  break;
380  case opacity:
381  embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
382  break;
383  }
384  ++iter;
385  }
386  // Reverse the offset of the clipView.
387  // The clipView's frame includes the final translate of the final transform matrix.
388  // So we need to revese this translate so the platform view can layout at the correct offset.
389  //
390  // Note that we don't apply this transform matrix the clippings because clippings happen on the
391  // mask view, whose origin is alwasy (0,0) to the flutter_view.
392  CATransform3D reverseTranslate =
393  CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0);
394  embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate);
395  clipView.maskView = maskView;
396 }
397 
398 void FlutterPlatformViewsController::CompositeWithParams(int view_id,
399  const EmbeddedViewParams& params) {
400  FML_DCHECK(flutter_view_);
401  CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());
402  UIView* touchInterceptor = touch_interceptors_[view_id].get();
403  touchInterceptor.layer.transform = CATransform3DIdentity;
404  touchInterceptor.frame = frame;
405  touchInterceptor.alpha = 1;
406 
407  const MutatorsStack& mutatorStack = params.mutatorsStack();
408  UIView* clippingView = root_views_[view_id].get();
409  // The frame of the clipping view should be the final bounding rect.
410  // Because the translate matrix in the Mutator Stack also includes the offset,
411  // when we apply the transforms matrix in |ApplyMutators|, we need
412  // to remember to do a reverse translate.
413  const SkRect& rect = params.finalBoundingRect();
414  CGFloat screenScale = [UIScreen mainScreen].scale;
415  clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
416  rect.width() / screenScale, rect.height() / screenScale);
417  ApplyMutators(mutatorStack, touchInterceptor);
418 }
419 
421  FML_DCHECK(flutter_view_);
422  // TODO(amirh): assert that this is running on the platform thread once we support the iOS
423  // embedded views thread configuration.
424 
425  // Do nothing if the view doesn't need to be composited.
426  if (views_to_recomposite_.count(view_id) == 0) {
427  return picture_recorders_[view_id]->getRecordingCanvas();
428  }
429  CompositeWithParams(view_id, current_composition_params_[view_id]);
430  views_to_recomposite_.erase(view_id);
431  return picture_recorders_[view_id]->getRecordingCanvas();
432 }
433 
435  UIView* flutter_view = flutter_view_.get();
436  for (UIView* sub_view in [flutter_view subviews]) {
437  [sub_view removeFromSuperview];
438  }
439  views_.clear();
440  composition_order_.clear();
441  active_composition_order_.clear();
442  picture_recorders_.clear();
443  platform_view_rtrees_.clear();
444  current_composition_params_.clear();
445  clip_count_.clear();
446  views_to_recomposite_.clear();
447  layer_pool_->RecycleLayers();
448 }
449 
451  UIView* platform_view = [views_[view_id].get() view];
452  UIScreen* screen = [UIScreen mainScreen];
453  CGRect platform_view_cgrect = [platform_view convertRect:platform_view.bounds
454  toView:flutter_view_];
455  return SkRect::MakeXYWH(platform_view_cgrect.origin.x * screen.scale, //
456  platform_view_cgrect.origin.y * screen.scale, //
457  platform_view_cgrect.size.width * screen.scale, //
458  platform_view_cgrect.size.height * screen.scale //
459  );
460 }
461 
462 bool FlutterPlatformViewsController::SubmitFrame(GrDirectContext* gr_context,
463  std::shared_ptr<IOSContext> ios_context,
464  std::unique_ptr<SurfaceFrame> frame) {
465  FML_DCHECK(flutter_view_);
466 
467  // Any UIKit related code has to run on main thread.
468  // When on a non-main thread, we only allow the rest of the method to run if there is no
469  // Pending UIView operations.
470  FML_DCHECK([[NSThread currentThread] isMainThread] || !HasPlatformViewThisOrNextFrame());
471 
472  DisposeViews();
473 
474  SkCanvas* background_canvas = frame->SkiaCanvas();
475 
476  // Resolve all pending GPU operations before allocating a new surface.
477  background_canvas->flush();
478  // Clipping the background canvas before drawing the picture recorders requires to
479  // save and restore the clip context.
480  SkAutoCanvasRestore save(background_canvas, /*doSave=*/true);
481  // Maps a platform view id to a vector of `FlutterPlatformViewLayer`.
482  LayersMap platform_view_layers;
483 
484  auto did_submit = true;
485  auto num_platform_views = composition_order_.size();
486 
487  for (size_t i = 0; i < num_platform_views; i++) {
488  int64_t platform_view_id = composition_order_[i];
489  sk_sp<RTree> rtree = platform_view_rtrees_[platform_view_id];
490  sk_sp<SkPicture> picture = picture_recorders_[platform_view_id]->finishRecordingAsPicture();
491 
492  // Check if the current picture contains overlays that intersect with the
493  // current platform view or any of the previous platform views.
494  for (size_t j = i + 1; j > 0; j--) {
495  int64_t current_platform_view_id = composition_order_[j - 1];
496  SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id);
497  std::list<SkRect> intersection_rects =
498  rtree->searchNonOverlappingDrawnRects(platform_view_rect);
499  auto allocation_size = intersection_rects.size();
500 
501  // For testing purposes, the overlay id is used to find the overlay view.
502  // This is the index of the layer for the current platform view.
503  auto overlay_id = platform_view_layers[current_platform_view_id].size();
504 
505  // If the max number of allocations per platform view is exceeded,
506  // then join all the rects into a single one.
507  //
508  // TODO(egarciad): Consider making this configurable.
509  // https://github.com/flutter/flutter/issues/52510
510  if (allocation_size > kMaxLayerAllocations) {
511  SkRect joined_rect;
512  for (const SkRect& rect : intersection_rects) {
513  joined_rect.join(rect);
514  }
515  // Replace the rects in the intersection rects list for a single rect that is
516  // the union of all the rects in the list.
517  intersection_rects.clear();
518  intersection_rects.push_back(joined_rect);
519  }
520  for (SkRect& joined_rect : intersection_rects) {
521  // Get the intersection rect between the current rect
522  // and the platform view rect.
523  joined_rect.intersect(platform_view_rect);
524  // Subpixels in the platform may not align with the canvas subpixels.
525  // To workaround it, round the floating point bounds and make the rect slighly larger.
526  // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}.
527  joined_rect.setLTRB(std::floor(joined_rect.left()), std::floor(joined_rect.top()),
528  std::ceil(joined_rect.right()), std::ceil(joined_rect.bottom()));
529  // Clip the background canvas, so it doesn't contain any of the pixels drawn
530  // on the overlay layer.
531  background_canvas->clipRect(joined_rect, SkClipOp::kDifference);
532  // Get a new host layer.
533  std::shared_ptr<FlutterPlatformViewLayer> layer = GetLayer(gr_context, //
534  ios_context, //
535  picture, //
536  joined_rect, //
537  current_platform_view_id, //
538  overlay_id //
539  );
540  did_submit &= layer->did_submit_last_frame;
541  platform_view_layers[current_platform_view_id].push_back(layer);
542  overlay_id++;
543  }
544  }
545  background_canvas->drawPicture(picture);
546  }
547  // If a layer was allocated in the previous frame, but it's not used in the current frame,
548  // then it can be removed from the scene.
549  RemoveUnusedLayers();
550  // Organize the layers by their z indexes.
551  BringLayersIntoView(platform_view_layers);
552  // Mark all layers as available, so they can be used in the next frame.
553  layer_pool_->RecycleLayers();
554  // Reset the composition order, so next frame starts empty.
555  composition_order_.clear();
556 
557  did_submit &= frame->Submit();
558 
559  // If the frame is submitted with embedded platform views,
560  // there should be a |[CATransaction begin]| call in this frame prior to all the drawing.
561  // If that case, we need to commit the transaction.
562  CommitCATransactionIfNeeded();
563  return did_submit;
564 }
565 
566 void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) {
567  FML_DCHECK(flutter_view_);
568  UIView* flutter_view = flutter_view_.get();
569  auto zIndex = 0;
570  // Clear the `active_composition_order_`, which will be populated down below.
571  active_composition_order_.clear();
572  for (size_t i = 0; i < composition_order_.size(); i++) {
573  int64_t platform_view_id = composition_order_[i];
574  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_map[platform_view_id];
575  UIView* platform_view_root = root_views_[platform_view_id].get();
576 
577  if (platform_view_root.superview != flutter_view) {
578  [flutter_view addSubview:platform_view_root];
579  } else {
580  platform_view_root.layer.zPosition = zIndex++;
581  }
582  for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
583  if ([layer->overlay_view_wrapper superview] != flutter_view) {
584  [flutter_view addSubview:layer->overlay_view_wrapper];
585  } else {
586  layer->overlay_view_wrapper.get().layer.zPosition = zIndex++;
587  }
588  }
589  active_composition_order_.push_back(platform_view_id);
590  }
591 }
592 
594  bool should_resubmit_frame,
595  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {}
596 
597 std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLayer(
598  GrDirectContext* gr_context,
599  std::shared_ptr<IOSContext> ios_context,
600  sk_sp<SkPicture> picture,
601  SkRect rect,
602  int64_t view_id,
603  int64_t overlay_id) {
604  FML_DCHECK(flutter_view_);
605  std::shared_ptr<FlutterPlatformViewLayer> layer = layer_pool_->GetLayer(gr_context, ios_context);
606 
607  UIView* overlay_view_wrapper = layer->overlay_view_wrapper.get();
608  auto screenScale = [UIScreen mainScreen].scale;
609  // Set the size of the overlay view wrapper.
610  // This wrapper view masks the overlay view.
611  overlay_view_wrapper.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
612  rect.width() / screenScale, rect.height() / screenScale);
613  // Set a unique view identifier, so the overlay wrapper can be identified in unit tests.
614  overlay_view_wrapper.accessibilityIdentifier =
615  [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id];
616 
617  UIView* overlay_view = layer->overlay_view.get();
618  // Set the size of the overlay view.
619  // This size is equal to the the device screen size.
620  overlay_view.frame = flutter_view_.get().bounds;
621 
622  std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
623  // If frame is null, AcquireFrame already printed out an error message.
624  if (!frame) {
625  return layer;
626  }
627  SkCanvas* overlay_canvas = frame->SkiaCanvas();
628  overlay_canvas->clear(SK_ColorTRANSPARENT);
629  // Offset the picture since its absolute position on the scene is determined
630  // by the position of the overlay view.
631  overlay_canvas->translate(-rect.x(), -rect.y());
632  overlay_canvas->drawPicture(picture);
633 
634  layer->did_submit_last_frame = frame->Submit();
635  return layer;
636 }
637 
638 void FlutterPlatformViewsController::RemoveUnusedLayers() {
639  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_pool_->GetUnusedLayers();
640  for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
641  [layer->overlay_view_wrapper removeFromSuperview];
642  }
643 
644  std::unordered_set<int64_t> composition_order_set;
645  for (int64_t view_id : composition_order_) {
646  composition_order_set.insert(view_id);
647  }
648  // Remove unused platform views.
649  for (int64_t view_id : active_composition_order_) {
650  if (composition_order_set.find(view_id) == composition_order_set.end()) {
651  UIView* platform_view_root = root_views_[view_id].get();
652  [platform_view_root removeFromSuperview];
653  }
654  }
655 }
656 
657 void FlutterPlatformViewsController::DisposeViews() {
658  if (views_to_dispose_.empty()) {
659  return;
660  }
661 
662  FML_DCHECK([[NSThread currentThread] isMainThread]);
663 
664  for (int64_t viewId : views_to_dispose_) {
665  UIView* root_view = root_views_[viewId].get();
666  [root_view removeFromSuperview];
667  views_.erase(viewId);
668  touch_interceptors_.erase(viewId);
669  root_views_.erase(viewId);
670  current_composition_params_.erase(viewId);
671  clip_count_.erase(viewId);
672  views_to_recomposite_.erase(viewId);
673  }
674  views_to_dispose_.clear();
675 }
676 
677 void FlutterPlatformViewsController::BeginCATransaction() {
678  FML_DCHECK([[NSThread currentThread] isMainThread]);
679  FML_DCHECK(!catransaction_added_);
680  [CATransaction begin];
681  catransaction_added_ = true;
682 }
683 
684 void FlutterPlatformViewsController::CommitCATransactionIfNeeded() {
685  if (catransaction_added_) {
686  FML_DCHECK([[NSThread currentThread] isMainThread]);
687  [CATransaction commit];
688  catransaction_added_ = false;
689  }
690 }
691 
692 } // namespace flutter
693 
694 // This recognizers delays touch events from being dispatched to the responder chain until it failed
695 // recognizing a gesture.
696 //
697 // We only fail this recognizer when asked to do so by the Flutter framework (which does so by
698 // invoking an acceptGesture method on the platform_views channel). And this is how we allow the
699 // Flutter framework to delay or prevent the embedded view from getting a touch sequence.
700 @interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
701 
702 // Indicates that if the `DelayingGestureRecognizer`'s state should be set to
703 // `UIGestureRecognizerStateEnded` during next `touchesEnded` call.
704 @property(nonatomic) bool shouldEndInNextTouchesEnded;
705 
706 // Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without
707 // setting the state to `UIGestureRecognizerStateEnded`.
708 @property(nonatomic) bool touchedEndedWithoutBlocking;
709 
710 - (instancetype)initWithTarget:(id)target
711  action:(SEL)action
712  forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
713 @end
714 
715 // While the DelayingGestureRecognizer is preventing touches from hitting the responder chain
716 // the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter
717 // framework). We use this gesture recognizer to dispatch the events directly to the FlutterView
718 // while during this phase.
719 //
720 // If the Flutter framework decides to dispatch events to the embedded view, we fail the
721 // DelayingGestureRecognizer which sends the events up the responder chain. But since the events
722 // are handled by the embedded view they are not delivered to the Flutter framework in this phase
723 // as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events
724 // directly to the FlutterView.
725 @interface ForwardingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
726 - (instancetype)initWithTarget:(id)target
727  platformViewsController:
728  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController;
729 @end
730 
734 }
735 - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
736  platformViewsController:
737  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController
738  gestureRecognizersBlockingPolicy:
740  self = [super initWithFrame:embeddedView.frame];
741  if (self) {
742  self.multipleTouchEnabled = YES;
743  embeddedView.autoresizingMask =
744  (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
745 
746  [self addSubview:embeddedView];
747 
748  ForwardingGestureRecognizer* forwardingRecognizer = [[[ForwardingGestureRecognizer alloc]
749  initWithTarget:self
750  platformViewsController:std::move(platformViewsController)] autorelease];
751 
752  _delayingRecognizer.reset([[DelayingGestureRecognizer alloc]
753  initWithTarget:self
754  action:nil
755  forwardingRecognizer:forwardingRecognizer]);
756  _blockingPolicy = blockingPolicy;
757 
758  [self addGestureRecognizer:_delayingRecognizer.get()];
759  [self addGestureRecognizer:forwardingRecognizer];
760  }
761  return self;
762 }
763 
764 - (void)releaseGesture {
765  _delayingRecognizer.get().state = UIGestureRecognizerStateFailed;
766 }
767 
768 - (void)blockGesture {
769  switch (_blockingPolicy) {
771  // We block all other gesture recognizers immediately in this policy.
772  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
773  break;
775  if (_delayingRecognizer.get().touchedEndedWithoutBlocking) {
776  // If touchesEnded of the `DelayingGesureRecognizer` has been already invoked,
777  // we want to set the state of the `DelayingGesureRecognizer` to
778  // `UIGestureRecognizerStateEnded` as soon as possible.
779  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
780  } else {
781  // If touchesEnded of the `DelayingGesureRecognizer` has not been invoked,
782  // We will set a flag to notify the `DelayingGesureRecognizer` to set the state to
783  // `UIGestureRecognizerStateEnded` when touchesEnded is called.
784  _delayingRecognizer.get().shouldEndInNextTouchesEnded = YES;
785  }
786  break;
787  default:
788  break;
789  }
790 }
791 
792 // We want the intercepting view to consume the touches and not pass the touches up to the parent
793 // view. Make the touch event method not call super will not pass the touches up to the parent view.
794 // Hence we overide the touch event methods and do nothing.
795 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
796 }
797 
798 - (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
799 }
800 
801 - (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
802 }
803 
804 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
805 }
806 
807 @end
808 
809 @implementation DelayingGestureRecognizer {
810  fml::scoped_nsobject<UIGestureRecognizer> _forwardingRecognizer;
811 }
812 
813 - (instancetype)initWithTarget:(id)target
814  action:(SEL)action
815  forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
816  self = [super initWithTarget:target action:action];
817  if (self) {
818  self.delaysTouchesBegan = YES;
819  self.delaysTouchesEnded = YES;
820  self.delegate = self;
821  self.shouldEndInNextTouchesEnded = NO;
822  self.touchedEndedWithoutBlocking = NO;
823  _forwardingRecognizer.reset([forwardingRecognizer retain]);
824  }
825  return self;
826 }
827 
828 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
829  shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
830  // The forwarding gesture recognizer should always get all touch events, so it should not be
831  // required to fail by any other gesture recognizer.
832  return otherGestureRecognizer != _forwardingRecognizer.get() && otherGestureRecognizer != self;
833 }
834 
835 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
836  shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
837  return otherGestureRecognizer == self;
838 }
839 
840 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
841  self.touchedEndedWithoutBlocking = NO;
842  [super touchesBegan:touches withEvent:event];
843 }
844 
845 - (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
846  if (self.shouldEndInNextTouchesEnded) {
847  self.state = UIGestureRecognizerStateEnded;
848  self.shouldEndInNextTouchesEnded = NO;
849  } else {
850  self.touchedEndedWithoutBlocking = YES;
851  }
852  [super touchesEnded:touches withEvent:event];
853 }
854 
855 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
856  self.state = UIGestureRecognizerStateFailed;
857 }
858 @end
859 
861  // Weak reference to FlutterPlatformViewsController. The FlutterPlatformViewsController has
862  // a reference to the FlutterViewController, where we can dispatch pointer events to.
863  //
864  // The lifecycle of FlutterPlatformViewsController is bind to FlutterEngine, which should always
865  // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of
866  // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController.
867  // Therefore, `_platformViewsController` should never be nullptr.
869  // Counting the pointers that has started in one touch sequence.
870  NSInteger _currentTouchPointersCount;
871 }
872 
873 - (instancetype)initWithTarget:(id)target
874  platformViewsController:
875  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController {
876  self = [super initWithTarget:target action:nil];
877  if (self) {
878  self.delegate = self;
879  FML_DCHECK(platformViewsController.get() != nullptr);
880  _platformViewsController = std::move(platformViewsController);
882  }
883  return self;
884 }
885 
886 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
887  [_platformViewsController->getFlutterViewController() touchesBegan:touches withEvent:event];
888  _currentTouchPointersCount += touches.count;
889 }
890 
891 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
892  [_platformViewsController->getFlutterViewController() touchesMoved:touches withEvent:event];
893 }
894 
895 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
896  [_platformViewsController->getFlutterViewController() touchesEnded:touches withEvent:event];
897  _currentTouchPointersCount -= touches.count;
898  // Touches in one touch sequence are sent to the touchesEnded method separately if different
899  // fingers stop touching the screen at different time. So one touchesEnded method triggering does
900  // not necessarially mean the touch sequence has ended. We Only set the state to
901  // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended.
902  if (_currentTouchPointersCount == 0) {
903  self.state = UIGestureRecognizerStateFailed;
904  }
905 }
906 
907 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
908  [_platformViewsController->getFlutterViewController() touchesCancelled:touches withEvent:event];
910  self.state = UIGestureRecognizerStateFailed;
911 }
912 
913 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
914  shouldRecognizeSimultaneouslyWithGestureRecognizer:
915  (UIGestureRecognizer*)otherGestureRecognizer {
916  return YES;
917 }
918 @end
G_BEGIN_DECLS FlValue * args
void RegisterViewFactory(NSObject< FlutterPlatformViewFactory > *factory, NSString *factoryId, FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy)
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy
void PrerollCompositeEmbeddedView(int view_id, std::unique_ptr< flutter::EmbeddedViewParams > params)
virtual std::unique_ptr< Surface > CreateGPUSurface(GrDirectContext *gr_context=nullptr)=0
#define FML_DCHECK(condition)
Definition: logging.h:86
NSObject< FlutterPlatformView > * GetPlatformViewByID(int view_id)
auto WeakPtr(std::shared_ptr< T > pointer)
std::vector< std::shared_ptr< FlutterPlatformViewLayer > > GetUnusedLayers()
void reset(NST * object=nil)
void ResetAnchor(CALayer *layer)
const std::vector< std::shared_ptr< Mutator > >::const_reverse_iterator Top() const
void SetFlutterViewController(UIViewController *flutter_view_controller)
Definition: ascii_trie.cc:9
void MergeWithLease(size_t lease_term)
const std::vector< std::shared_ptr< Mutator > >::const_iterator Begin() const
std::shared_ptr< flutter::FlutterPlatformViewsController > _platformViewsController
PostPrerollResult PostPrerollAction(fml::RefPtr< fml::RasterThreadMerger > raster_thread_merger)
const SkSize & sizePoints() const
const std::vector< std::shared_ptr< Mutator > >::const_iterator End() const
bool SubmitFrame(GrDirectContext *gr_context, std::shared_ptr< IOSContext > ios_context, std::unique_ptr< SurfaceFrame > frame)
void(^ FlutterResult)(id _Nullable result)
FLUTTER_EXPORT NSObject const * FlutterMethodNotImplemented
CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix &matrix)
GdkEventButton * event
Definition: fl_view.cc:62
FlutterPlatformViewGestureRecognizersBlockingPolicy
#define FML_CHECK(condition)
Definition: logging.h:68
const SkRect & finalBoundingRect() const
void OnMethodCall(FlutterMethodCall *call, FlutterResult &result)
const MutatorsStack & mutatorsStack() const
NSInteger _currentTouchPointersCount
std::shared_ptr< FlutterPlatformViewLayer > GetLayer(GrDirectContext *gr_context, std::shared_ptr< IOSContext > ios_context)
void EndFrame(bool should_resubmit_frame, fml::RefPtr< fml::RasterThreadMerger > raster_thread_merger)
void ExtendLeaseTo(size_t lease_term)
const std::vector< std::shared_ptr< Mutator > >::const_reverse_iterator Bottom() const