8 #include <QuartzCore/QuartzCore.h>
10 #include "flutter/fml/logging.h"
11 #include "flutter/shell/platform/embedder/embedder.h"
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]);
23 offset_ = layer->offset;
27 const std::vector<FlutterPlatformViewMutation>& mutations,
30 : identifier_(identifier), mutations_(mutations), offset_(offset), size_(size) {}
40 if (
self = [super init]) {
41 _flutterView = flutterView;
46 - (void)frameCleanup {
51 - (BOOL)cleanupScheduled {
57 - (void)processMouseMoveEvent:(NSEvent*)event
59 overlayRegion:(const std::vector<CGRect>&)region {
62 [[NSRunLoop mainRunLoop] performBlock:^{
73 NSPoint point = [view convertPoint:event.locationInWindow fromView:nil];
76 for (
const auto& r : region) {
77 if (CGRectContainsPoint(r, point)) {
78 [_flutterView cursorUpdate:event];
87 [platformView.window invalidateCursorRectsForView:platformView];
125 - (NSView*)hitTest:(NSPoint)point {
126 NSView* res = [
super hitTest:point];
127 return res !=
self ? res : nil;
148 - (instancetype)initWithFrame:(NSRect)frameRect {
149 if (
self = [super initWithFrame:frameRect]) {
150 self.wantsLayer = YES;
162 - (NSView*)hitTest:(NSPoint)point {
163 NSView* res = [
super hitTest:point];
164 return res !=
self ? res : nil;
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;
175 maskLayer.path = path;
176 maskLayer.transform = CATransform3DMakeTranslation(-origin.x, -origin.y, 0);
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;
195 bool AffineTransformIsOnlyScaleOrTranslate(
const CGAffineTransform& transform) {
196 return transform.b == 0 && transform.c == 0;
199 bool IsZeroSize(
const FlutterSize size) {
200 return size.width == 0 && size.height == 0;
203 CGRect FromFlutterRect(
const FlutterRect& rect) {
204 return CGRectMake(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
207 FlutterRect ToFlutterRect(
const CGRect& rect) {
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,
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) <
224 bool RoundRectCornerIntersects(
const FlutterRoundedRect& roundRect,
const FlutterRect& 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);
231 CGPoint relative_top_left =
232 CGPointMake(rect.left - inner_top_left.x, rect.top - inner_top_left.y);
235 if (relative_top_left.x < 0 && relative_top_left.y < 0) {
236 if (!PointInsideEllipse(relative_top_left, roundRect.upper_left_corner_radius)) {
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);
247 CGPoint relative_top_right =
248 CGPointMake(rect.right - inner_top_right.x, rect.top - inner_top_right.y);
251 if (relative_top_right.x > 0 && relative_top_right.y < 0) {
252 if (!PointInsideEllipse(relative_top_right, roundRect.upper_right_corner_radius)) {
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);
263 CGPoint relative_bottom_left =
264 CGPointMake(rect.left - inner_bottom_left.x, rect.bottom - inner_bottom_left.y);
267 if (relative_bottom_left.x < 0 && relative_bottom_left.y > 0) {
268 if (!PointInsideEllipse(relative_bottom_left, roundRect.lower_left_corner_radius)) {
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);
279 CGPoint relative_bottom_right =
280 CGPointMake(rect.right - inner_bottom_right.x, rect.bottom - inner_bottom_right.y);
283 if (relative_bottom_right.x > 0 && relative_bottom_right.y > 0) {
284 if (!PointInsideEllipse(relative_bottom_right, roundRect.lower_right_corner_radius)) {
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);
300 CGMutablePathRef path = CGPathCreateMutable();
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;
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);
326 using MutationVector = std::vector<FlutterPlatformViewMutation>;
332 MutationVector MutationsForPlatformView(
const MutationVector& mutationsIn,
float scale) {
333 MutationVector mutations(mutationsIn);
335 mutations.insert(mutations.begin(), {
336 .type = kFlutterPlatformViewMutationTypeTransformation,
338 .scaleX = 1.0 / scale,
339 .scaleY = 1.0 / scale,
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);
355 case kFlutterPlatformViewMutationTypeClipRect:
356 case kFlutterPlatformViewMutationTypeClipRoundedRect:
357 case kFlutterPlatformViewMutationTypeOpacity:
365 float OpacityFromMutations(
const MutationVector& mutations) {
367 for (
auto mutation : mutations) {
368 switch (mutation.type) {
369 case kFlutterPlatformViewMutationTypeOpacity:
370 opacity *= mutation.opacity;
372 case kFlutterPlatformViewMutationTypeClipRect:
373 case kFlutterPlatformViewMutationTypeClipRoundedRect:
374 case kFlutterPlatformViewMutationTypeTransformation:
382 CGRect MasterClipFromMutations(CGRect bounds,
const MutationVector& mutations) {
385 CGRect master_clip = bounds;
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);
397 case kFlutterPlatformViewMutationTypeClipRoundedRect: {
398 CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
399 CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rounded_rect.rect),
401 master_clip = CGRectIntersection(rect, master_clip);
404 case kFlutterPlatformViewMutationTypeTransformation:
405 transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform);
407 case kFlutterPlatformViewMutationTypeOpacity:
416 FlutterRoundedRect rrect;
417 CGAffineTransform transform;
421 NSMutableArray* ClipPathFromMutations(CGRect master_clip,
const MutationVector& mutations) {
422 std::vector<ClipRoundedRect> rounded_rects;
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});
432 case kFlutterPlatformViewMutationTypeTransformation:
433 transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform);
435 case kFlutterPlatformViewMutationTypeClipRect: {
436 CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
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}},
446 case kFlutterPlatformViewMutationTypeOpacity:
451 NSMutableArray* paths = [NSMutableArray array];
452 for (
const auto& r : rounded_rects) {
453 bool requiresPath = !AffineTransformIsOnlyScaleOrTranslate(r.transform);
455 CGAffineTransform inverse = CGAffineTransformInvert(r.transform);
458 CGRect localMasterClip = CGRectApplyAffineTransform(master_clip, inverse);
459 requiresPath = RoundRectCornerIntersects(r.rrect, ToFlutterRect(localMasterClip));
465 CGPathRef path = PathFromRoundedRect(r.rrect);
466 CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &r.transform);
467 [paths addObject:(__bridge id)transformedPath];
468 CGPathRelease(transformedPath);
480 - (NSView*)hitTest:(NSPoint)point {
488 return _platformView;
491 - (NSMutableArray*)pathClipViews {
492 return _pathClipViews;
495 - (NSView*)platformViewContainer {
496 return _platformViewContainer;
499 - (instancetype)initWithPlatformView:(NSView*)
platformView {
500 return [
self initWithPlatformView:platformView cursorCoordiator:nil];
503 - (instancetype)initWithPlatformView:(NSView*)
platformView
505 if (
self = [super initWithFrame:NSZeroRect]) {
507 _pathClipViews = [NSMutableArray array];
508 _cursorCoordinator = coordinator;
509 self.wantsLayer = YES;
510 self.clipsToBounds = YES;
513 [
self addSubview:_trackingAreaContainer];
515 NSTrackingAreaOptions options = NSTrackingMouseMoved | NSTrackingInVisibleRect |
516 NSTrackingEnabledDuringMouseDrag | NSTrackingActiveAlways;
517 _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
521 [_trackingAreaContainer addTrackingArea:_trackingArea];
527 self->_hitTestIgnoreRegion.clear();
530 - (void)addHitTestIgnoreRegion:(CGRect)region {
531 self->_hitTestIgnoreRegion.push_back(region);
534 - (void)mouseMoved:(NSEvent*)event {
535 [_cursorCoordinator processMouseMoveEvent:event
537 overlayRegion:_hitTestIgnoreRegion];
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)) {
549 return [
super hitTest:point];
557 - (CGFloat)contentsScale {
558 return self.superview != nil ?
self.superview.layer.contentsScale : 1.0;
562 - (void)updatePathClipViewsWithPaths:(NSArray*)paths {
564 while (_pathClipViews.count > paths.count) {
565 NSView* view = _pathClipViews.lastObject;
566 [view removeFromSuperview];
567 [_pathClipViews removeLastObject];
570 for (
size_t i = _pathClipViews.count; i < paths.count; ++i) {
571 NSView* superView = _pathClipViews.count == 0 ? self : _pathClipViews.lastObject;
573 [_pathClipViews addObject:pathClipView];
574 [superView addSubview:pathClipView];
577 for (
size_t i = 0; i < _pathClipViews.count; ++i) {
579 pathClipView.frame =
self.bounds;
580 [pathClipView maskToPath:(__bridge CGPathRef)[paths objectAtIndex:i]
581 withOrigin:self.frame.origin];
590 - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds
591 transformedBounds:(CGRect)transformedBounds
592 transform:(CATransform3D)transform
593 clipRect:(CGRect)clipRect {
595 if (_platformViewContainer == nil) {
597 _platformViewContainer.wantsLayer = YES;
601 NSView* containerSuperview = _pathClipViews.count == 0 ? self : _pathClipViews.lastObject;
602 [containerSuperview addSubview:_platformViewContainer];
603 _platformViewContainer.frame =
self.bounds;
608 if (_platformView.superview == nil) {
609 [_platformViewContainer addSubview:_platformView];
616 _platformViewContainer.subviews.firstObject.frame = untransformedBounds;
619 CATransform3D translation =
620 CATransform3DMakeTranslation(-transformedBounds.origin.x, -transformedBounds.origin.y, 0);
621 transform = CATransform3DConcat(transform, translation);
622 _platformViewContainer.layer.sublayerTransform = transform;
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;
642 - (void)applyFlutterLayer:(const
flutter::PlatformViewLayer*)layer {
645 CGFloat scale = [
self contentsScale];
646 MutationVector mutations = MutationsForPlatformView(layer->mutations(), scale);
648 CATransform3D finalTransform = CATransformFromMutations(mutations);
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;
659 self.layer.opacity = OpacityFromMutations(mutations);
662 CGRect masterClip = MasterClipFromMutations(finalBoundingRect, mutations);
663 if (CGRectIsNull(masterClip)) {
670 NSMutableArray* paths = ClipPathFromMutations(masterClip, mutations);
671 [
self updatePathClipViewsWithPaths:paths];
674 [
self updatePlatformViewWithBounds:untransformedBoundingRect
675 transformedBounds:finalBoundingRect
676 transform:finalTransform
677 clipRect:masterClip];
679 [
self addSubview:_trackingAreaContainer positioned:(NSWindowAbove)relativeTo:nil];
680 _trackingAreaContainer.frame =
self.bounds;