[macOS] Refactor PlatformView mutators (flutter/engine#42130)

This refactors `[FlutterMutatorView applyLayer:]` to split out orthogonal behaviour into smaller functions for better readability/maintainability.

Extracts:
* General transform composition to CATransformFromMutations
* Opacity handling
* MasterClip computation
* RoundedRectClip computation

This change is a cleanup change for readability and makes no semantic changes to how mutator views are applied. Existing unit tests in [FlutterMutatorViewTest.mm](https://github.com/flutter/engine/blob/main/shell/platform/darwin/macos/framework/Source/FlutterMutatorViewTest.mm) are expected to pass without changes.

This is refactoring prior to applying a fix for rotation handling.

Issue: https://github.com/flutter/flutter/issues/124490

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Chris Bracken 2023-05-18 13:41:05 -07:00 committed by GitHub
parent d35d4dc958
commit 49afcc0101

View File

@ -88,28 +88,6 @@ FlutterRect ToFlutterRect(const CGRect& rect) {
};
}
using MutationVector = std::vector<FlutterPlatformViewMutation>;
/// 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<FlutterPlatformViewMutation>;
/// 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<ClipRoundedRect> 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<std::pair<FlutterRoundedRect, CGAffineTransform>> 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];