mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
d35d4dc958
commit
49afcc0101
@ -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];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user