diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm index 6bb35e269e2..15435779a63 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm @@ -88,28 +88,6 @@ FlutterRect ToFlutterRect(const CGRect& rect) { }; } -using MutationVector = std::vector; - -/// Returns a vector of FlutterPlatformViewMutation object pointers associated with a platform view. -/// The transforms sent from the engine include a transform from logical to physical coordinates. -/// Since Cocoa deals only in logical points, this function prepends a scale transform that scales -/// back from physical to logical coordinates to compensate. -MutationVector MutationsForPlatformView(const FlutterPlatformView* view, float scale) { - MutationVector mutations; - mutations.reserve(view->mutations_count + 1); - mutations.push_back({ - .type = kFlutterPlatformViewMutationTypeTransformation, - .transformation{ - .scaleX = 1.0 / scale, - .scaleY = 1.0 / scale, - }, - }); - for (size_t i = 0; i < view->mutations_count; ++i) { - mutations.push_back(*view->mutations[i]); - } - return mutations; -} - /// Returns whether the point is inside ellipse with given radius (centered at 0, 0). bool PointInsideEllipse(const CGPoint& point, const FlutterSize& radius) { return (point.x * point.x) / (radius.width * radius.width) + @@ -211,6 +189,144 @@ CGPathRef PathFromRoundedRect(const FlutterRoundedRect& roundedRect) { CGPathCloseSubpath(path); return path; } + +using MutationVector = std::vector; + +/// Returns a vector of FlutterPlatformViewMutation object pointers associated with a platform view. +/// The transforms sent from the engine include a transform from logical to physical coordinates. +/// Since Cocoa deals only in logical points, this function prepends a scale transform that scales +/// back from physical to logical coordinates to compensate. +MutationVector MutationsForPlatformView(const FlutterPlatformView* view, float scale) { + MutationVector mutations; + mutations.reserve(view->mutations_count + 1); + mutations.push_back({ + .type = kFlutterPlatformViewMutationTypeTransformation, + .transformation{ + .scaleX = 1.0 / scale, + .scaleY = 1.0 / scale, + }, + }); + for (size_t i = 0; i < view->mutations_count; ++i) { + mutations.push_back(*view->mutations[i]); + } + return mutations; +} + +/// Returns the composition of all transformation mutations in the mutations vector. +CATransform3D CATransformFromMutations(const MutationVector& mutations) { + CATransform3D transform = CATransform3DIdentity; + for (auto mutation : mutations) { + switch (mutation.type) { + case kFlutterPlatformViewMutationTypeTransformation: { + CATransform3D mutationTransform = ToCATransform3D(mutation.transformation); + transform = CATransform3DConcat(mutationTransform, transform); + break; + } + case kFlutterPlatformViewMutationTypeClipRect: + case kFlutterPlatformViewMutationTypeClipRoundedRect: + case kFlutterPlatformViewMutationTypeOpacity: + break; + } + } + return transform; +} + +/// Returns the opacity for all opacity mutations in the mutations vector. +float OpacityFromMutations(const MutationVector& mutations) { + float opacity = 1.0; + for (auto mutation : mutations) { + switch (mutation.type) { + case kFlutterPlatformViewMutationTypeOpacity: + opacity *= mutation.opacity; + break; + case kFlutterPlatformViewMutationTypeClipRect: + case kFlutterPlatformViewMutationTypeClipRoundedRect: + case kFlutterPlatformViewMutationTypeTransformation: + break; + } + } + return opacity; +} + +/// Returns the clip rect generated by the intersection of clips in the mutations vector. +CGRect MasterClipFromMutations(CGRect bounds, const MutationVector& mutations) { + // Master clip in global logical coordinates. This is intersection of all clip rectangles + // present in mutators. + CGRect master_clip = bounds; + + // Create the initial transform. + CATransform3D transform = CATransform3DIdentity; + for (auto mutation : mutations) { + switch (mutation.type) { + case kFlutterPlatformViewMutationTypeClipRect: { + CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rect), + CATransform3DGetAffineTransform(transform)); + master_clip = CGRectIntersection(rect, master_clip); + break; + } + case kFlutterPlatformViewMutationTypeClipRoundedRect: { + CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform); + CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rounded_rect.rect), + affineTransform); + master_clip = CGRectIntersection(rect, master_clip); + break; + } + case kFlutterPlatformViewMutationTypeTransformation: + transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform); + break; + case kFlutterPlatformViewMutationTypeOpacity: + break; + } + } + return master_clip; +} + +/// A rounded rectangle and transform associated with it. +typedef struct { + FlutterRoundedRect rrect; + CGAffineTransform transform; +} ClipRoundedRect; + +/// Returns the set of all rounded rect paths generated by clips in the mutations vector. +NSMutableArray* RoundedRectClipsFromMutations(CGRect master_clip, const MutationVector& mutations) { + std::vector rounded_rects; + + CATransform3D transform = CATransform3DIdentity; + for (auto mutation : mutations) { + switch (mutation.type) { + case kFlutterPlatformViewMutationTypeClipRoundedRect: { + CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform); + rounded_rects.push_back({mutation.clip_rounded_rect, affineTransform}); + break; + } + case kFlutterPlatformViewMutationTypeTransformation: + transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform); + break; + case kFlutterPlatformViewMutationTypeClipRect: + case kFlutterPlatformViewMutationTypeOpacity: + break; + } + } + + NSMutableArray* paths = [NSMutableArray array]; + for (const auto& r : rounded_rects) { + CGAffineTransform inverse = CGAffineTransformInvert(r.transform); + // Transform master clip to clip rect coordinates and check if this view intersects one of the + // corners, which means we need to use path clipping. + CGRect localMasterClip = CGRectApplyAffineTransform(master_clip, inverse); + + // Only clip to rounded rectangle path if the view intersects some of the round corners. If + // not, clipping to masterClip is enough. + if (RoundRectCornerIntersects(r.rrect, ToFlutterRect(localMasterClip))) { + CGPathRef path = PathFromRoundedRect(r.rrect); + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &r.transform); + [paths addObject:(__bridge id)transformedPath]; + CGPathRelease(transformedPath); + CGPathRelease(path); + } + } + return paths; +} } // namespace @implementation FlutterMutatorView @@ -253,86 +369,36 @@ CGPathRef PathFromRoundedRect(const FlutterRoundedRect& roundedRect) { /// If clipping to path is needed, CAShapeLayer(s) will be used as mask. /// Clipping to round rect only clips to path if round corners are intersected. - (void)applyFlutterLayer:(const FlutterLayer*)layer { + // Compute the untransformed bounding rect for the platform view in logical pixels. + // FlutterLayer.size is in physical pixels but Cocoa uses logical points. CGFloat scale = [self contentsScale]; - auto mutations = MutationsForPlatformView(layer->platform_view, scale); + MutationVector mutations = MutationsForPlatformView(layer->platform_view, scale); - // Platform view transform after applying all transformation mutations. - CATransform3D finalTransform = CATransform3DIdentity; - for (auto mutation : mutations) { - if (mutation.type == kFlutterPlatformViewMutationTypeTransformation) { - CATransform3D mutationTransform = ToCATransform3D(mutation.transformation); - finalTransform = CATransform3DConcat(mutationTransform, finalTransform); - } - } + CATransform3D finalTransform = CATransformFromMutations(mutations); // Compute the untransformed bounding rect for the platform view in logical pixels. // FlutterLayer.size is in physical pixels but Cocoa uses logical points. CGRect untransformedBoundingRect = CGRectMake(0, 0, layer->size.width / scale, layer->size.height / scale); - CGRect finalBoundingRect = CGRectApplyAffineTransform( untransformedBoundingRect, CATransform3DGetAffineTransform(finalTransform)); - self.frame = finalBoundingRect; - // Master clip in global logical coordinates. This is intersection of all clip rectangles - // present in mutators. - CGRect masterClip = finalBoundingRect; - - self.layer.opacity = 1.0; - - // Gathered pairs of rounded rect in local coordinates + appropriate transform. - std::vector> roundedRects; - - // Create the initial transform. - CATransform3D transform = CATransform3DIdentity; - for (auto mutation : mutations) { - if (mutation.type == kFlutterPlatformViewMutationTypeTransformation) { - transform = CATransform3DConcat(ToCATransform3D(mutation.transformation), transform); - } else if (mutation.type == kFlutterPlatformViewMutationTypeClipRect) { - CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rect), - CATransform3DGetAffineTransform(transform)); - masterClip = CGRectIntersection(rect, masterClip); - } else if (mutation.type == kFlutterPlatformViewMutationTypeClipRoundedRect) { - CGAffineTransform affineTransform = CATransform3DGetAffineTransform(transform); - roundedRects.push_back(std::make_pair(mutation.clip_rounded_rect, affineTransform)); - CGRect rect = CGRectApplyAffineTransform(FromFlutterRect(mutation.clip_rounded_rect.rect), - affineTransform); - masterClip = CGRectIntersection(rect, masterClip); - } else if (mutation.type == kFlutterPlatformViewMutationTypeOpacity) { - self.layer.opacity *= mutation.opacity; - } - } + // Compute the layer opacity. + self.layer.opacity = OpacityFromMutations(mutations); + // Compute the master clip in global logical coordinates. + CGRect masterClip = MasterClipFromMutations(finalBoundingRect, mutations); if (CGRectIsNull(masterClip)) { self.hidden = YES; return; } - self.hidden = NO; /// Paths in global logical coordinates that need to be clipped to. - NSMutableArray* paths = [NSMutableArray array]; - - for (const auto& r : roundedRects) { - CGAffineTransform inverse = CGAffineTransformInvert(r.second); - // Transform master clip to clip rect coordinates and check if this view intersects one of the - // corners, which means we need to use path clipping. - CGRect localMasterClip = CGRectApplyAffineTransform(masterClip, inverse); - - // Only clip to rounded rectangle path if the view intersects some of the round corners. If - // not, clipping to masterClip is enough. - if (RoundRectCornerIntersects(r.first, ToFlutterRect(localMasterClip))) { - CGPathRef path = PathFromRoundedRect(r.first); - CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &r.second); - [paths addObject:(__bridge id)transformedPath]; - CGPathRelease(transformedPath); - CGPathRelease(path); - } - } + NSMutableArray* paths = RoundedRectClipsFromMutations(masterClip, mutations); // Add / remove path clip views depending on the number of paths. - while (_pathClipViews.count > paths.count) { NSView* view = _pathClipViews.lastObject; [view removeFromSuperview];