7 #include <QuartzCore/QuartzCore.h>
11 #include "flutter/fml/logging.h"
12 #include "flutter/shell/platform/embedder/embedder.h"
53 - (instancetype)initWithFrame:(NSRect)frameRect {
54 if (
self = [super initWithFrame:frameRect]) {
55 self.wantsLayer = YES;
69 - (void)maskToPath:(CGPathRef)path withOrigin:(CGPoint)origin {
70 CAShapeLayer* maskLayer =
self.layer.mask;
71 if (maskLayer == nil) {
72 maskLayer = [CAShapeLayer layer];
73 self.layer.mask = maskLayer;
75 maskLayer.path = path;
76 maskLayer.transform = CATransform3DMakeTranslation(-origin.x, -origin.y, 0);
82 CATransform3D ToCATransform3D(
const FlutterTransformation& t) {
83 CATransform3D transform = CATransform3DIdentity;
84 transform.m11 = t.scaleX;
85 transform.m21 = t.skewX;
86 transform.m41 = t.transX;
87 transform.m14 = t.pers0;
88 transform.m12 = t.skewY;
89 transform.m22 = t.scaleY;
90 transform.m42 = t.transY;
91 transform.m24 = t.pers1;
95 bool AffineTransformIsOnlyScaleOrTranslate(
const CGAffineTransform& transform) {
96 return transform.b == 0 && transform.c == 0;
99 bool IsZeroSize(
const FlutterSize size) {
100 return size.width == 0 && size.height == 0;
103 CGRect FromFlutterRect(
const FlutterRect& rect) {
104 return CGRectMake(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
107 FlutterRect ToFlutterRect(
const CGRect& rect) {
109 .left = rect.origin.x,
110 .top = rect.origin.y,
111 .right = rect.origin.x + rect.size.width,
112 .bottom = rect.origin.y + rect.size.height,
118 bool PointInsideEllipse(
const CGPoint& point,
const FlutterSize& radius) {
119 return (point.x * point.x) / (radius.width * radius.width) +
120 (point.y * point.y) / (radius.height * radius.height) <
124 bool RoundRectCornerIntersects(
const FlutterRoundedRect& roundRect,
const FlutterRect& rect) {
126 CGPoint inner_top_left =
127 CGPointMake(roundRect.rect.left + roundRect.upper_left_corner_radius.width,
128 roundRect.rect.top + roundRect.upper_left_corner_radius.height);
131 CGPoint relative_top_left =
132 CGPointMake(rect.left - inner_top_left.x, rect.top - inner_top_left.y);
135 if (relative_top_left.x < 0 && relative_top_left.y < 0) {
136 if (!PointInsideEllipse(relative_top_left, roundRect.upper_left_corner_radius)) {
142 CGPoint inner_top_right =
143 CGPointMake(roundRect.rect.right - roundRect.upper_right_corner_radius.width,
144 roundRect.rect.top + roundRect.upper_right_corner_radius.height);
147 CGPoint relative_top_right =
148 CGPointMake(rect.right - inner_top_right.x, rect.top - inner_top_right.y);
151 if (relative_top_right.x > 0 && relative_top_right.y < 0) {
152 if (!PointInsideEllipse(relative_top_right, roundRect.upper_right_corner_radius)) {
158 CGPoint inner_bottom_left =
159 CGPointMake(roundRect.rect.left + roundRect.lower_left_corner_radius.width,
160 roundRect.rect.bottom - roundRect.lower_left_corner_radius.height);
163 CGPoint relative_bottom_left =
164 CGPointMake(rect.left - inner_bottom_left.x, rect.bottom - inner_bottom_left.y);
167 if (relative_bottom_left.x < 0 && relative_bottom_left.y > 0) {
168 if (!PointInsideEllipse(relative_bottom_left, roundRect.lower_left_corner_radius)) {
174 CGPoint inner_bottom_right =
175 CGPointMake(roundRect.rect.right - roundRect.lower_right_corner_radius.width,
176 roundRect.rect.bottom - roundRect.lower_right_corner_radius.height);
179 CGPoint relative_bottom_right =
180 CGPointMake(rect.right - inner_bottom_right.x, rect.bottom - inner_bottom_right.y);
183 if (relative_bottom_right.x > 0 && relative_bottom_right.y > 0) {
184 if (!PointInsideEllipse(relative_bottom_right, roundRect.lower_right_corner_radius)) {
192 CGPathRef PathFromRoundedRect(
const FlutterRoundedRect& roundedRect) {
193 if (IsZeroSize(roundedRect.lower_left_corner_radius) &&
194 IsZeroSize(roundedRect.lower_right_corner_radius) &&
195 IsZeroSize(roundedRect.upper_left_corner_radius) &&
196 IsZeroSize(roundedRect.upper_right_corner_radius)) {
197 return CGPathCreateWithRect(FromFlutterRect(roundedRect.rect),
nullptr);
200 CGMutablePathRef path = CGPathCreateMutable();
202 const auto& rect = roundedRect.rect;
203 const auto& topLeft = roundedRect.upper_left_corner_radius;
204 const auto& topRight = roundedRect.upper_right_corner_radius;
205 const auto& bottomLeft = roundedRect.lower_left_corner_radius;
206 const auto& bottomRight = roundedRect.lower_right_corner_radius;
208 CGPathMoveToPoint(path,
nullptr, rect.left + topLeft.width, rect.top);
209 CGPathAddLineToPoint(path,
nullptr, rect.right - topRight.width, rect.top);
210 CGPathAddCurveToPoint(path,
nullptr, rect.right, rect.top, rect.right, rect.top + topRight.height,
211 rect.right, rect.top + topRight.height);
212 CGPathAddLineToPoint(path,
nullptr, rect.right, rect.bottom - bottomRight.height);
213 CGPathAddCurveToPoint(path,
nullptr, rect.right, rect.bottom, rect.right - bottomRight.width,
214 rect.bottom, rect.right - bottomRight.width, rect.bottom);
215 CGPathAddLineToPoint(path,
nullptr, rect.left + bottomLeft.width, rect.bottom);
216 CGPathAddCurveToPoint(path,
nullptr, rect.left, rect.bottom, rect.left,
217 rect.bottom - bottomLeft.height, rect.left,
218 rect.bottom - bottomLeft.height);
219 CGPathAddLineToPoint(path,
nullptr, rect.left, rect.top + topLeft.height);
220 CGPathAddCurveToPoint(path,
nullptr, rect.left, rect.top, rect.left + topLeft.width, rect.top,
221 rect.left + topLeft.width, rect.top);
222 CGPathCloseSubpath(path);
226 using MutationVector = std::vector<FlutterPlatformViewMutation>;
232 MutationVector MutationsForPlatformView(
const FlutterPlatformView* view,
float scale) {
233 MutationVector mutations;
234 mutations.reserve(view->mutations_count + 1);
235 mutations.push_back({
236 .type = kFlutterPlatformViewMutationTypeTransformation,
238 .scaleX = 1.0 / scale,
239 .scaleY = 1.0 / scale,
242 for (
size_t i = 0; i < view->mutations_count; ++i) {
243 mutations.push_back(*view->mutations[i]);
249 CATransform3D CATransformFromMutations(
const MutationVector& mutations) {
250 CATransform3D transform = CATransform3DIdentity;
251 for (
auto mutation : mutations) {
252 switch (mutation.type) {
253 case kFlutterPlatformViewMutationTypeTransformation: {
254 CATransform3D mutationTransform = ToCATransform3D(mutation.transformation);
255 transform = CATransform3DConcat(mutationTransform, transform);
258 case kFlutterPlatformViewMutationTypeClipRect:
259 case kFlutterPlatformViewMutationTypeClipRoundedRect:
260 case kFlutterPlatformViewMutationTypeOpacity:
268 float OpacityFromMutations(
const MutationVector& mutations) {
270 for (
auto mutation : mutations) {
271 switch (mutation.type) {
272 case kFlutterPlatformViewMutationTypeOpacity:
273 opacity *= mutation.opacity;
275 case kFlutterPlatformViewMutationTypeClipRect:
276 case kFlutterPlatformViewMutationTypeClipRoundedRect:
277 case kFlutterPlatformViewMutationTypeTransformation:
285 CGRect MasterClipFromMutations(CGRect bounds,
const MutationVector& mutations) {
288 CGRect master_clip = bounds;
291 CATransform3D transform = CATransform3DIdentity;
292 for (
auto mutation : mutations) {
293 switch (mutation.type) {
294 case kFlutterPlatformViewMutationTypeClipRect: {
295 CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rect),
296 CATransform3DGetAffineTransform(transform));
297 master_clip = CGRectIntersection(rect, master_clip);
300 case kFlutterPlatformViewMutationTypeClipRoundedRect: {
301 CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
302 CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rounded_rect.rect),
304 master_clip = CGRectIntersection(rect, master_clip);
307 case kFlutterPlatformViewMutationTypeTransformation:
308 transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform);
310 case kFlutterPlatformViewMutationTypeOpacity:
319 FlutterRoundedRect rrect;
320 CGAffineTransform transform;
324 NSMutableArray* ClipPathFromMutations(CGRect master_clip,
const MutationVector& mutations) {
325 std::vector<ClipRoundedRect> rounded_rects;
327 CATransform3D transform = CATransform3DIdentity;
328 for (
auto mutation : mutations) {
329 switch (mutation.type) {
330 case kFlutterPlatformViewMutationTypeClipRoundedRect: {
331 CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
332 rounded_rects.push_back({mutation.clip_rounded_rect, affineTransform});
335 case kFlutterPlatformViewMutationTypeTransformation:
336 transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform);
338 case kFlutterPlatformViewMutationTypeClipRect: {
339 CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform);
341 if (!AffineTransformIsOnlyScaleOrTranslate(affineTransform)) {
342 rounded_rects.push_back(
343 {FlutterRoundedRect{mutation.clip_rect, FlutterSize{0, 0}, FlutterSize{0, 0},
344 FlutterSize{0, 0}, FlutterSize{0, 0}},
349 case kFlutterPlatformViewMutationTypeOpacity:
354 NSMutableArray* paths = [NSMutableArray array];
355 for (
const auto& r : rounded_rects) {
356 bool requiresPath = !AffineTransformIsOnlyScaleOrTranslate(r.transform);
358 CGAffineTransform inverse = CGAffineTransformInvert(r.transform);
361 CGRect localMasterClip = CGRectApplyAffineTransform(master_clip, inverse);
362 requiresPath = RoundRectCornerIntersects(r.rrect, ToFlutterRect(localMasterClip));
368 CGPathRef path = PathFromRoundedRect(r.rrect);
369 CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &r.transform);
370 [paths addObject:(__bridge id)transformedPath];
371 CGPathRelease(transformedPath);
382 return _platformView;
385 - (NSMutableArray*)pathClipViews {
386 return _pathClipViews;
389 - (NSView*)platformViewContainer {
390 return _platformViewContainer;
393 - (instancetype)initWithPlatformView:(NSView*)
platformView {
394 if (
self = [super initWithFrame:NSZeroRect]) {
396 _pathClipViews = [NSMutableArray array];
397 self.wantsLayer = YES;
402 - (NSView*)hitTest:(NSPoint)point {
411 - (CGFloat)contentsScale {
412 return self.superview != nil ?
self.superview.layer.contentsScale : 1.0;
416 - (void)updatePathClipViewsWithPaths:(NSArray*)paths {
418 while (_pathClipViews.count > paths.count) {
419 NSView* view = _pathClipViews.lastObject;
420 [view removeFromSuperview];
421 [_pathClipViews removeLastObject];
424 for (
size_t i = _pathClipViews.count; i < paths.count; ++i) {
425 NSView* superView = _pathClipViews.count == 0 ? self : _pathClipViews.lastObject;
427 [_pathClipViews addObject:pathClipView];
428 [superView addSubview:pathClipView];
431 for (
size_t i = 0; i < _pathClipViews.count; ++i) {
433 pathClipView.frame =
self.bounds;
434 [pathClipView maskToPath:(__bridge CGPathRef)[paths objectAtIndex:i]
435 withOrigin:self.frame.origin];
444 - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds
445 transformedBounds:(CGRect)transformedBounds
446 transform:(CATransform3D)transform
447 clipRect:(CGRect)clipRect {
449 if (_platformViewContainer == nil) {
451 _platformViewContainer.wantsLayer = YES;
455 NSView* containerSuperview = _pathClipViews.count == 0 ? self : _pathClipViews.lastObject;
456 [containerSuperview addSubview:_platformViewContainer];
457 _platformViewContainer.frame =
self.bounds;
460 [_platformViewContainer addSubview:_platformView];
461 _platformView.frame = untransformedBounds;
464 CATransform3D translation =
465 CATransform3DMakeTranslation(-transformedBounds.origin.x, -transformedBounds.origin.y, 0);
466 transform = CATransform3DConcat(transform, translation);
467 _platformViewContainer.layer.sublayerTransform = transform;
471 if (!CGRectEqualToRect(clipRect, transformedBounds)) {
472 FML_DCHECK(
self.subviews.count == 1);
473 auto subview =
self.subviews.firstObject;
474 FML_DCHECK(subview.frame.origin.x == 0 && subview.frame.origin.y == 0);
475 subview.frame = CGRectMake(transformedBounds.origin.x - clipRect.origin.x,
476 transformedBounds.origin.y - clipRect.origin.y,
477 subview.frame.size.width, subview.frame.size.height);
478 self.frame = clipRect;
485 - (void)applyFlutterLayer:(
const FlutterLayer*)layer {
488 CGFloat scale = [
self contentsScale];
489 MutationVector mutations = MutationsForPlatformView(layer->platform_view, scale);
491 CATransform3D finalTransform = CATransformFromMutations(mutations);
495 CGRect untransformedBoundingRect =
496 CGRectMake(0, 0, layer->size.width / scale, layer->size.height / scale);
497 CGRect finalBoundingRect = CGRectApplyAffineTransform(
498 untransformedBoundingRect, CATransform3DGetAffineTransform(finalTransform));
499 self.frame = finalBoundingRect;
502 self.layer.opacity = OpacityFromMutations(mutations);
505 CGRect masterClip = MasterClipFromMutations(finalBoundingRect, mutations);
506 if (CGRectIsNull(masterClip)) {
513 NSMutableArray* paths = ClipPathFromMutations(masterClip, mutations);
514 [
self updatePathClipViewsWithPaths:paths];
517 [
self updatePlatformViewWithBounds:untransformedBoundingRect
518 transformedBounds:finalBoundingRect
519 transform:finalTransform
520 clipRect:masterClip];