Flutter macOS Embedder
FlutterMutatorView.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 
7 
8 #include <QuartzCore/QuartzCore.h>
9 
10 #include "flutter/fml/logging.h"
11 #include "flutter/shell/platform/embedder/embedder.h"
12 
14 
15 namespace flutter {
16 PlatformViewLayer::PlatformViewLayer(const FlutterLayer* layer) {
17  FML_CHECK(layer->type == kFlutterLayerContentTypePlatformView);
18  const auto* platform_view = layer->platform_view;
19  identifier_ = platform_view->identifier;
20  for (size_t i = 0; i < platform_view->mutations_count; i++) {
21  mutations_.push_back(*platform_view->mutations[i]);
22  }
23  offset_ = layer->offset;
24  size_ = layer->size;
25 }
26 PlatformViewLayer::PlatformViewLayer(FlutterPlatformViewIdentifier identifier,
27  const std::vector<FlutterPlatformViewMutation>& mutations,
28  FlutterPoint offset,
29  FlutterSize size)
30  : identifier_(identifier), mutations_(mutations), offset_(offset), size_(size) {}
31 } // namespace flutter
32 
33 @implementation FlutterCursorCoordinator {
34  __weak FlutterView* _flutterView;
35  BOOL _cleanupScheduled;
37 }
38 
39 - (FlutterCursorCoordinator*)initWithFlutterView:(FlutterView*)flutterView {
40  if (self = [super init]) {
41  _flutterView = flutterView;
42  }
43  return self;
44 }
45 
46 - (void)frameCleanup {
47  _cleanupScheduled = NO;
48  _mouseMoveHandled = NO;
49 }
50 
51 - (BOOL)cleanupScheduled {
52  return _cleanupScheduled;
53 }
54 
55 // Processes the mouse event from given mutator view. This is called for each mutator view, in
56 // z-order, from the top most down.
57 - (void)processMouseMoveEvent:(NSEvent*)event
58  forMutatorView:(FlutterMutatorView*)view
59  overlayRegion:(const std::vector<CGRect>&)region {
60  // [self frameCleanup] will be called once after current run loop turn.
61  if (!_cleanupScheduled) {
62  [[NSRunLoop mainRunLoop] performBlock:^{
63  [self frameCleanup];
64  }];
65  _cleanupScheduled = YES;
66  }
67 
68  // Mouse move was already handled by a mutator view above.
69  if (_mouseMoveHandled) {
70  return;
71  }
72 
73  NSPoint point = [view convertPoint:event.locationInWindow fromView:nil];
74 
75  // If the mouse is above overlay region restore current Flutter cursor.
76  for (const auto& r : region) {
77  if (CGRectContainsPoint(r, point)) {
78  [_flutterView cursorUpdate:event];
79  _mouseMoveHandled = YES;
80  return;
81  }
82  }
83  NSView* platformView = view.platformView;
84  // It is possible that Flutter changed mouse cursor while the mouse was inside
85  // cursor rect. Unfocused NSTextField uses legacy cursor rects for changing
86  // its cursor.
87  [platformView.window invalidateCursorRectsForView:platformView];
88  _mouseMoveHandled = YES;
89 }
90 @end
91 
92 @interface FlutterMutatorView () {
93  // Each of these views clips to a CGPathRef. These views, if present,
94  // are nested (first is child of FlutterMutatorView and last is parent of
95  // _platformView).
96  NSMutableArray* _pathClipViews;
97 
98  // View right above the platform view. Used to apply the final transform
99  // (sans the translation) to the platform view.
101 
102  NSView* _platformView;
103 
105 
106  // Container view that hosts the tracking area. Must be above platform view
107  // so that it gets the mouseMove event first.
109 
110  // Tracking area used to update cursor when moving over overlay region.
111  NSTrackingArea* _trackingArea;
112 
113  // Region of the overlay that should be ignored for hit testing.
114  std::vector<CGRect> _hitTestIgnoreRegion;
115 }
116 
117 @end
118 
119 /// Superview container for platform views, to which sublayer transforms are applied.
120 @interface FlutterPlatformViewContainer : NSView
121 @end
122 
123 @implementation FlutterPlatformViewContainer
124 
125 - (NSView*)hitTest:(NSPoint)point {
126  NSView* res = [super hitTest:point];
127  return res != self ? res : nil;
128 }
129 
130 - (BOOL)isFlipped {
131  // Flutter transforms assume a coordinate system with an upper-left corner origin, with y
132  // coordinate values increasing downwards. This affects the view, view transforms, and
133  // sublayerTransforms.
134  return YES;
135 }
136 
137 @end
138 
139 /// View that clips that content to a specific CGPathRef.
140 /// Clipping is done through a CAShapeLayer mask, which avoids the need to
141 /// rasterize the mask.
142 @interface FlutterPathClipView : NSView
143 
144 @end
145 
146 @implementation FlutterPathClipView
147 
148 - (instancetype)initWithFrame:(NSRect)frameRect {
149  if (self = [super initWithFrame:frameRect]) {
150  self.wantsLayer = YES;
151  }
152  return self;
153 }
154 
155 - (BOOL)isFlipped {
156  // Flutter transforms assume a coordinate system with an upper-left corner origin, with y
157  // coordinate values increasing downwards. This affects the view, view transforms, and
158  // sublayerTransforms.
159  return YES;
160 }
161 
162 - (NSView*)hitTest:(NSPoint)point {
163  NSView* res = [super hitTest:point];
164  return res != self ? res : nil;
165 }
166 
167 /// Clip the view to the given path. Offset top left corner of platform view
168 /// in global logical coordinates.
169 - (void)maskToPath:(CGPathRef)path withOrigin:(CGPoint)origin {
170  CAShapeLayer* maskLayer = self.layer.mask;
171  if (maskLayer == nil) {
172  maskLayer = [CAShapeLayer layer];
173  self.layer.mask = maskLayer;
174  }
175  maskLayer.path = path;
176  maskLayer.transform = CATransform3DMakeTranslation(-origin.x, -origin.y, 0);
177 }
178 
179 @end
180 
181 namespace {
182 CATransform3D ToCATransform3D(const FlutterTransformation& t) {
183  CATransform3D transform = CATransform3DIdentity;
184  transform.m11 = t.scaleX;
185  transform.m21 = t.skewX;
186  transform.m41 = t.transX;
187  transform.m14 = t.pers0;
188  transform.m12 = t.skewY;
189  transform.m22 = t.scaleY;
190  transform.m42 = t.transY;
191  transform.m24 = t.pers1;
192  return transform;
193 }
194 
195 bool AffineTransformIsOnlyScaleOrTranslate(const CGAffineTransform& transform) {
196  return transform.b == 0 && transform.c == 0;
197 }
198 
199 bool IsZeroSize(const FlutterSize size) {
200  return size.width == 0 && size.height == 0;
201 }
202 
203 CGRect FromFlutterRect(const FlutterRect& rect) {
204  return CGRectMake(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
205 }
206 
207 FlutterRect ToFlutterRect(const CGRect& rect) {
208  return FlutterRect{
209  .left = rect.origin.x,
210  .top = rect.origin.y,
211  .right = rect.origin.x + rect.size.width,
212  .bottom = rect.origin.y + rect.size.height,
213 
214  };
215 }
216 
217 /// Returns whether the point is inside ellipse with given radius (centered at 0, 0).
218 bool PointInsideEllipse(const CGPoint& point, const FlutterSize& radius) {
219  return (point.x * point.x) / (radius.width * radius.width) +
220  (point.y * point.y) / (radius.height * radius.height) <
221  1.0;
222 }
223 
224 bool RoundRectCornerIntersects(const FlutterRoundedRect& roundRect, const FlutterRect& rect) {
225  // Inner coordinate of the top left corner of the round rect.
226  CGPoint inner_top_left =
227  CGPointMake(roundRect.rect.left + roundRect.upper_left_corner_radius.width,
228  roundRect.rect.top + roundRect.upper_left_corner_radius.height);
229 
230  // Position of `rect` corner relative to inner_top_left.
231  CGPoint relative_top_left =
232  CGPointMake(rect.left - inner_top_left.x, rect.top - inner_top_left.y);
233 
234  // `relative_top_left` is in upper left quadrant.
235  if (relative_top_left.x < 0 && relative_top_left.y < 0) {
236  if (!PointInsideEllipse(relative_top_left, roundRect.upper_left_corner_radius)) {
237  return true;
238  }
239  }
240 
241  // Inner coordinate of the top right corner of the round rect.
242  CGPoint inner_top_right =
243  CGPointMake(roundRect.rect.right - roundRect.upper_right_corner_radius.width,
244  roundRect.rect.top + roundRect.upper_right_corner_radius.height);
245 
246  // Positon of `rect` corner relative to inner_top_right.
247  CGPoint relative_top_right =
248  CGPointMake(rect.right - inner_top_right.x, rect.top - inner_top_right.y);
249 
250  // `relative_top_right` is in top right quadrant.
251  if (relative_top_right.x > 0 && relative_top_right.y < 0) {
252  if (!PointInsideEllipse(relative_top_right, roundRect.upper_right_corner_radius)) {
253  return true;
254  }
255  }
256 
257  // Inner coordinate of the bottom left corner of the round rect.
258  CGPoint inner_bottom_left =
259  CGPointMake(roundRect.rect.left + roundRect.lower_left_corner_radius.width,
260  roundRect.rect.bottom - roundRect.lower_left_corner_radius.height);
261 
262  // Position of `rect` corner relative to inner_bottom_left.
263  CGPoint relative_bottom_left =
264  CGPointMake(rect.left - inner_bottom_left.x, rect.bottom - inner_bottom_left.y);
265 
266  // `relative_bottom_left` is in bottom left quadrant.
267  if (relative_bottom_left.x < 0 && relative_bottom_left.y > 0) {
268  if (!PointInsideEllipse(relative_bottom_left, roundRect.lower_left_corner_radius)) {
269  return true;
270  }
271  }
272 
273  // Inner coordinate of the bottom right corner of the round rect.
274  CGPoint inner_bottom_right =
275  CGPointMake(roundRect.rect.right - roundRect.lower_right_corner_radius.width,
276  roundRect.rect.bottom - roundRect.lower_right_corner_radius.height);
277 
278  // Position of `rect` corner relative to inner_bottom_right.
279  CGPoint relative_bottom_right =
280  CGPointMake(rect.right - inner_bottom_right.x, rect.bottom - inner_bottom_right.y);
281 
282  // `relative_bottom_right` is in bottom right quadrant.
283  if (relative_bottom_right.x > 0 && relative_bottom_right.y > 0) {
284  if (!PointInsideEllipse(relative_bottom_right, roundRect.lower_right_corner_radius)) {
285  return true;
286  }
287  }
288 
289  return false;
290 }
291 
292 CGPathRef PathFromRoundedRect(const FlutterRoundedRect& roundedRect) {
293  if (IsZeroSize(roundedRect.lower_left_corner_radius) &&
294  IsZeroSize(roundedRect.lower_right_corner_radius) &&
295  IsZeroSize(roundedRect.upper_left_corner_radius) &&
296  IsZeroSize(roundedRect.upper_right_corner_radius)) {
297  return CGPathCreateWithRect(FromFlutterRect(roundedRect.rect), nullptr);
298  }
299 
300  CGMutablePathRef path = CGPathCreateMutable();
301 
302  const auto& rect = roundedRect.rect;
303  const auto& topLeft = roundedRect.upper_left_corner_radius;
304  const auto& topRight = roundedRect.upper_right_corner_radius;
305  const auto& bottomLeft = roundedRect.lower_left_corner_radius;
306  const auto& bottomRight = roundedRect.lower_right_corner_radius;
307 
308  CGPathMoveToPoint(path, nullptr, rect.left + topLeft.width, rect.top);
309  CGPathAddLineToPoint(path, nullptr, rect.right - topRight.width, rect.top);
310  CGPathAddCurveToPoint(path, nullptr, rect.right, rect.top, rect.right, rect.top + topRight.height,
311  rect.right, rect.top + topRight.height);
312  CGPathAddLineToPoint(path, nullptr, rect.right, rect.bottom - bottomRight.height);
313  CGPathAddCurveToPoint(path, nullptr, rect.right, rect.bottom, rect.right - bottomRight.width,
314  rect.bottom, rect.right - bottomRight.width, rect.bottom);
315  CGPathAddLineToPoint(path, nullptr, rect.left + bottomLeft.width, rect.bottom);
316  CGPathAddCurveToPoint(path, nullptr, rect.left, rect.bottom, rect.left,
317  rect.bottom - bottomLeft.height, rect.left,
318  rect.bottom - bottomLeft.height);
319  CGPathAddLineToPoint(path, nullptr, rect.left, rect.top + topLeft.height);
320  CGPathAddCurveToPoint(path, nullptr, rect.left, rect.top, rect.left + topLeft.width, rect.top,
321  rect.left + topLeft.width, rect.top);
322  CGPathCloseSubpath(path);
323  return path;
324 }
325 
326 using MutationVector = std::vector<FlutterPlatformViewMutation>;
327 
328 /// Returns a vector of FlutterPlatformViewMutation object pointers associated with a platform view.
329 /// The transforms sent from the engine include a transform from logical to physical coordinates.
330 /// Since Cocoa deals only in logical points, this function prepends a scale transform that scales
331 /// back from physical to logical coordinates to compensate.
332 MutationVector MutationsForPlatformView(const MutationVector& mutationsIn, float scale) {
333  MutationVector mutations(mutationsIn);
334 
335  mutations.insert(mutations.begin(), {
336  .type = kFlutterPlatformViewMutationTypeTransformation,
337  .transformation{
338  .scaleX = 1.0 / scale,
339  .scaleY = 1.0 / scale,
340  },
341  });
342  return mutations;
343 }
344 
345 /// Returns the composition of all transformation mutations in the mutations vector.
346 CATransform3D CATransformFromMutations(const MutationVector& mutations) {
347  CATransform3D transform = CATransform3DIdentity;
348  for (auto mutation : mutations) {
349  switch (mutation.type) {
350  case kFlutterPlatformViewMutationTypeTransformation: {
351  CATransform3D mutationTransform = ToCATransform3D(mutation.transformation);
352  transform = CATransform3DConcat(mutationTransform, transform);
353  break;
354  }
355  case kFlutterPlatformViewMutationTypeClipRect:
356  case kFlutterPlatformViewMutationTypeClipRoundedRect:
357  case kFlutterPlatformViewMutationTypeOpacity:
358  break;
359  }
360  }
361  return transform;
362 }
363 
364 /// Returns the opacity for all opacity mutations in the mutations vector.
365 float OpacityFromMutations(const MutationVector& mutations) {
366  float opacity = 1.0;
367  for (auto mutation : mutations) {
368  switch (mutation.type) {
369  case kFlutterPlatformViewMutationTypeOpacity:
370  opacity *= mutation.opacity;
371  break;
372  case kFlutterPlatformViewMutationTypeClipRect:
373  case kFlutterPlatformViewMutationTypeClipRoundedRect:
374  case kFlutterPlatformViewMutationTypeTransformation:
375  break;
376  }
377  }
378  return opacity;
379 }
380 
381 /// Returns the clip rect generated by the intersection of clips in the mutations vector.
382 CGRect MasterClipFromMutations(CGRect bounds, const MutationVector& mutations) {
383  // Master clip in global logical coordinates. This is intersection of all clip rectangles
384  // present in mutators.
385  CGRect master_clip = bounds;
386 
387  // Create the initial transform.
388  CATransform3D transform = CATransform3DIdentity;
389  for (auto mutation : mutations) {
390  switch (mutation.type) {
391  case kFlutterPlatformViewMutationTypeClipRect: {
392  CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rect),
393  CATransform3DGetAffineTransform(transform));
394  master_clip = CGRectIntersection(rect, master_clip);
395  break;
396  }
397  case kFlutterPlatformViewMutationTypeClipRoundedRect: {
398  CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
399  CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rounded_rect.rect),
400  affineTransform);
401  master_clip = CGRectIntersection(rect, master_clip);
402  break;
403  }
404  case kFlutterPlatformViewMutationTypeTransformation:
405  transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform);
406  break;
407  case kFlutterPlatformViewMutationTypeOpacity:
408  break;
409  }
410  }
411  return master_clip;
412 }
413 
414 /// A rounded rectangle and transform associated with it.
415 typedef struct {
416  FlutterRoundedRect rrect;
417  CGAffineTransform transform;
418 } ClipRoundedRect;
419 
420 /// Returns the set of all rounded rect paths generated by clips in the mutations vector.
421 NSMutableArray* ClipPathFromMutations(CGRect master_clip, const MutationVector& mutations) {
422  std::vector<ClipRoundedRect> rounded_rects;
423 
424  CATransform3D transform = CATransform3DIdentity;
425  for (auto mutation : mutations) {
426  switch (mutation.type) {
427  case kFlutterPlatformViewMutationTypeClipRoundedRect: {
428  CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
429  rounded_rects.push_back({mutation.clip_rounded_rect, affineTransform});
430  break;
431  }
432  case kFlutterPlatformViewMutationTypeTransformation:
433  transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform);
434  break;
435  case kFlutterPlatformViewMutationTypeClipRect: {
436  CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
437  // Shearing or rotation requires path clipping.
438  if (!AffineTransformIsOnlyScaleOrTranslate(affineTransform)) {
439  rounded_rects.push_back(
440  {FlutterRoundedRect{mutation.clip_rect, FlutterSize{0, 0}, FlutterSize{0, 0},
441  FlutterSize{0, 0}, FlutterSize{0, 0}},
442  affineTransform});
443  }
444  break;
445  }
446  case kFlutterPlatformViewMutationTypeOpacity:
447  break;
448  }
449  }
450 
451  NSMutableArray* paths = [NSMutableArray array];
452  for (const auto& r : rounded_rects) {
453  bool requiresPath = !AffineTransformIsOnlyScaleOrTranslate(r.transform);
454  if (!requiresPath) {
455  CGAffineTransform inverse = CGAffineTransformInvert(r.transform);
456  // Transform master clip to clip rect coordinates and check if this view intersects one of the
457  // corners, which means we need to use path clipping.
458  CGRect localMasterClip = CGRectApplyAffineTransform(master_clip, inverse);
459  requiresPath = RoundRectCornerIntersects(r.rrect, ToFlutterRect(localMasterClip));
460  }
461 
462  // Only clip to rounded rectangle path if the view intersects some of the round corners. If
463  // not, clipping to masterClip is enough.
464  if (requiresPath) {
465  CGPathRef path = PathFromRoundedRect(r.rrect);
466  CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &r.transform);
467  [paths addObject:(__bridge id)transformedPath];
468  CGPathRelease(transformedPath);
469  CGPathRelease(path);
470  }
471  }
472  return paths;
473 }
474 } // namespace
475 
476 @interface FlutterTrackingAreaContainer : NSView
477 @end
478 
479 @implementation FlutterTrackingAreaContainer
480 - (NSView*)hitTest:(NSPoint)point {
481  return nil;
482 }
483 @end
484 
485 @implementation FlutterMutatorView
486 
487 - (NSView*)platformView {
488  return _platformView;
489 }
490 
491 - (NSMutableArray*)pathClipViews {
492  return _pathClipViews;
493 }
494 
495 - (NSView*)platformViewContainer {
496  return _platformViewContainer;
497 }
498 
499 - (instancetype)initWithPlatformView:(NSView*)platformView {
500  return [self initWithPlatformView:platformView cursorCoordiator:nil];
501 }
502 
503 - (instancetype)initWithPlatformView:(NSView*)platformView
504  cursorCoordiator:(FlutterCursorCoordinator*)coordinator {
505  if (self = [super initWithFrame:NSZeroRect]) {
506  _platformView = platformView;
507  _pathClipViews = [NSMutableArray array];
508  _cursorCoordinator = coordinator;
509  self.wantsLayer = YES;
510  self.clipsToBounds = YES;
511 
512  _trackingAreaContainer = [[FlutterTrackingAreaContainer alloc] initWithFrame:NSZeroRect];
513  [self addSubview:_trackingAreaContainer];
514 
515  NSTrackingAreaOptions options = NSTrackingMouseMoved | NSTrackingInVisibleRect |
516  NSTrackingEnabledDuringMouseDrag | NSTrackingActiveAlways;
517  _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
518  options:options
519  owner:self
520  userInfo:nil];
521  [_trackingAreaContainer addTrackingArea:_trackingArea];
522  }
523  return self;
524 }
525 
527  self->_hitTestIgnoreRegion.clear();
528 }
529 
530 - (void)addHitTestIgnoreRegion:(CGRect)region {
531  self->_hitTestIgnoreRegion.push_back(region);
532 }
533 
534 - (void)mouseMoved:(NSEvent*)event {
535  [_cursorCoordinator processMouseMoveEvent:event
536  forMutatorView:self
537  overlayRegion:_hitTestIgnoreRegion];
538 }
539 
540 - (NSView*)hitTest:(NSPoint)point {
541  CGPoint localPoint = point;
542  localPoint.x -= self.frame.origin.x;
543  localPoint.y -= self.frame.origin.y;
544  for (const auto& region : _hitTestIgnoreRegion) {
545  if (CGRectContainsPoint(region, localPoint)) {
546  return nil;
547  }
548  }
549  return [super hitTest:point];
550 }
551 
552 - (BOOL)isFlipped {
553  return YES;
554 }
555 
556 /// Returns the scale factor to translate logical pixels to physical pixels for this view.
557 - (CGFloat)contentsScale {
558  return self.superview != nil ? self.superview.layer.contentsScale : 1.0;
559 }
560 
561 /// Updates the nested stack of clip views that host the platform view.
562 - (void)updatePathClipViewsWithPaths:(NSArray*)paths {
563  // Remove path clip views depending on the number of paths.
564  while (_pathClipViews.count > paths.count) {
565  NSView* view = _pathClipViews.lastObject;
566  [view removeFromSuperview];
567  [_pathClipViews removeLastObject];
568  }
569  // Otherwise, add path clip views to the end.
570  for (size_t i = _pathClipViews.count; i < paths.count; ++i) {
571  NSView* superView = _pathClipViews.count == 0 ? self : _pathClipViews.lastObject;
572  FlutterPathClipView* pathClipView = [[FlutterPathClipView alloc] initWithFrame:self.bounds];
573  [_pathClipViews addObject:pathClipView];
574  [superView addSubview:pathClipView];
575  }
576  // Update bounds and apply clip paths.
577  for (size_t i = 0; i < _pathClipViews.count; ++i) {
578  FlutterPathClipView* pathClipView = _pathClipViews[i];
579  pathClipView.frame = self.bounds;
580  [pathClipView maskToPath:(__bridge CGPathRef)[paths objectAtIndex:i]
581  withOrigin:self.frame.origin];
582  }
583 }
584 
585 /// Updates the PlatformView and PlatformView container views.
586 ///
587 /// Re-nests _platformViewContainer in the innermost clip view, applies transforms to the underlying
588 /// CALayer, adds the platform view as a subview of the container, and sets the axis-aligned clip
589 /// rect around the tranformed view.
590 - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds
591  transformedBounds:(CGRect)transformedBounds
592  transform:(CATransform3D)transform
593  clipRect:(CGRect)clipRect {
594  // Create the PlatformViewContainer view if necessary.
595  if (_platformViewContainer == nil) {
596  _platformViewContainer = [[FlutterPlatformViewContainer alloc] initWithFrame:self.bounds];
597  _platformViewContainer.wantsLayer = YES;
598  }
599 
600  // Nest the PlatformViewContainer view in the innermost path clip view.
601  NSView* containerSuperview = _pathClipViews.count == 0 ? self : _pathClipViews.lastObject;
602  [containerSuperview addSubview:_platformViewContainer];
603  _platformViewContainer.frame = self.bounds;
604 
605  // Nest the platform view in the PlatformViewContainer, but only if the view doesn't have a
606  // superview yet. Sometimes the platform view reparents itself (WKWebView entering FullScreen)
607  // in which case do not forcefully move it back.
608  if (_platformView.superview == nil) {
609  [_platformViewContainer addSubview:_platformView];
610  }
611 
612  // Originally first subview would be the _platformView. However during WKWebView full screen
613  // the platform view gets replaced with a placeholder. Given that _platformViewContainer does
614  // not contain any other views it is safe to assume that any subview found can be treated
615  // as the platform view.
616  _platformViewContainer.subviews.firstObject.frame = untransformedBounds;
617 
618  // Transform for the platform view is finalTransform adjusted for bounding rect origin.
619  CATransform3D translation =
620  CATransform3DMakeTranslation(-transformedBounds.origin.x, -transformedBounds.origin.y, 0);
621  transform = CATransform3DConcat(transform, translation);
622  _platformViewContainer.layer.sublayerTransform = transform;
623 
624  // By default NSView clips children to frame. If masterClip is tighter than mutator view frame,
625  // the frame is set to masterClip and child offset adjusted to compensate for the difference.
626  if (!CGRectEqualToRect(clipRect, transformedBounds)) {
627  NSMutableArray<NSView*>* subviews = [NSMutableArray arrayWithArray:self.subviews];
628  [subviews removeObject:_trackingAreaContainer];
629  FML_DCHECK(subviews.count == 1);
630  auto subview = subviews.firstObject;
631  FML_DCHECK(subview.frame.origin.x == 0 && subview.frame.origin.y == 0);
632  subview.frame = CGRectMake(transformedBounds.origin.x - clipRect.origin.x,
633  transformedBounds.origin.y - clipRect.origin.y,
634  subview.frame.size.width, subview.frame.size.height);
635  self.frame = clipRect;
636  }
637 }
638 
639 /// Whenever possible view will be clipped using layer bounds.
640 /// If clipping to path is needed, CAShapeLayer(s) will be used as mask.
641 /// Clipping to round rect only clips to path if round corners are intersected.
642 - (void)applyFlutterLayer:(const flutter::PlatformViewLayer*)layer {
643  // Compute the untransformed bounding rect for the platform view in logical pixels.
644  // FlutterLayer.size is in physical pixels but Cocoa uses logical points.
645  CGFloat scale = [self contentsScale];
646  MutationVector mutations = MutationsForPlatformView(layer->mutations(), scale);
647 
648  CATransform3D finalTransform = CATransformFromMutations(mutations);
649 
650  // Compute the untransformed bounding rect for the platform view in logical pixels.
651  // FlutterLayer.size is in physical pixels but Cocoa uses logical points.
652  CGRect untransformedBoundingRect =
653  CGRectMake(0, 0, layer->size().width / scale, layer->size().height / scale);
654  CGRect finalBoundingRect = CGRectApplyAffineTransform(
655  untransformedBoundingRect, CATransform3DGetAffineTransform(finalTransform));
656  self.frame = finalBoundingRect;
657 
658  // Compute the layer opacity.
659  self.layer.opacity = OpacityFromMutations(mutations);
660 
661  // Compute the master clip in global logical coordinates.
662  CGRect masterClip = MasterClipFromMutations(finalBoundingRect, mutations);
663  if (CGRectIsNull(masterClip)) {
664  self.hidden = YES;
665  return;
666  }
667  self.hidden = NO;
668 
669  /// Paths in global logical coordinates that need to be clipped to.
670  NSMutableArray* paths = ClipPathFromMutations(masterClip, mutations);
671  [self updatePathClipViewsWithPaths:paths];
672 
673  /// Update PlatformViewContainer, PlatformView, and apply transforms and axis-aligned clip rect.
674  [self updatePlatformViewWithBounds:untransformedBoundingRect
675  transformedBounds:finalBoundingRect
676  transform:finalTransform
677  clipRect:masterClip];
678 
679  [self addSubview:_trackingAreaContainer positioned:(NSWindowAbove)relativeTo:nil];
680  _trackingAreaContainer.frame = self.bounds;
681 }
682 
683 @end
FlutterMutatorView.h
FlutterMutatorView
Definition: FlutterMutatorView.h:63
FlutterCursorCoordinator
Definition: FlutterMutatorView.h:45
FlutterMutatorView::platformView
NSView * platformView
Returns wrapped platform view.
Definition: FlutterMutatorView.h:72
FlutterMutatorView()::_trackingAreaContainer
NSView * _trackingAreaContainer
Definition: FlutterMutatorView.mm:108
FlutterMutatorView()::_platformViewContainer
NSView * _platformViewContainer
Definition: FlutterMutatorView.mm:100
FlutterMutatorView()::_platformView
NSView * _platformView
Definition: FlutterMutatorView.mm:102
_cleanupScheduled
BOOL _cleanupScheduled
Definition: FlutterMutatorView.mm:33
FlutterPlatformViewContainer
Superview container for platform views, to which sublayer transforms are applied.
Definition: FlutterMutatorView.mm:120
-[FlutterMutatorView resetHitTestRegion]
void resetHitTestRegion()
Resets hit hit testing region for this mutator view.
Definition: FlutterMutatorView.mm:526
FlutterMutatorView()::_trackingArea
NSTrackingArea * _trackingArea
Definition: FlutterMutatorView.mm:111
_mouseMoveHandled
BOOL _mouseMoveHandled
Definition: FlutterMutatorView.mm:36
flutter
Definition: AccessibilityBridgeMac.h:16
FlutterTrackingAreaContainer
Definition: FlutterMutatorView.mm:476
NSView+ClipsToBounds.h
flutter::PlatformViewLayer::PlatformViewLayer
PlatformViewLayer(const FlutterLayer *_Nonnull layer)
FlutterMutatorView()::_cursorCoordinator
FlutterCursorCoordinator * _cursorCoordinator
Definition: FlutterMutatorView.mm:104
FlutterView
Definition: FlutterView.h:35
FlutterMutatorView()::_pathClipViews
NSMutableArray * _pathClipViews
Definition: FlutterMutatorView.mm:96
FlutterMutatorView()::_hitTestIgnoreRegion
std::vector< CGRect > _hitTestIgnoreRegion
Definition: FlutterMutatorView.mm:114
FlutterPathClipView
Definition: FlutterMutatorView.mm:142
FlutterView.h