| 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
5 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.h"
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
7
8 #include <QuartzCore/QuartzCore.h>
9
10 #include "flutter/fml/logging.h"
11 #include "flutter/shell/platform/embedder/embedder.h"
12
13 #import "flutter/shell/platform/darwin/macos/framework/Source/NSView+ClipsToBounds.h"
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;
36 BOOL _mouseMoveHandled;
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.
100 NSView* _platformViewContainer;
101
102 NSView* _platformView;
103
104 FlutterCursorCoordinator* _cursorCoordinator;
105
106 // Container view that hosts the tracking area. Must be above platform view
107 // so that it gets the mouseMove event first.
108 NSView* _trackingAreaContainer;
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
526 - (void)resetHitTestRegion {
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