mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[ios_platform_view] only recycle maskView when the view is applying mutators (flutter/engine#41573)
A mistake was introduced in https://github.com/flutter/engine/pull/39498 where the maskViews are already recycles each frame. Sometimes a PlatformView does not need to be re-composite: (https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm#L398-L401), so the mask view for such PlatformView should not be recycled. This PR changed the `recycleMaskViews` API to allow individual maskviews to be recycled. `ApplyMutator` then only recycle the maskView for that particular PlatformView. The MaskViewPool is also reworked to be simpler. - The pool now contains a single set of mask views, there is no index counter needed. - When a maskView is needed, try to get it from the pool. - If pool is empty, create a new view. - If pool has an available maskview, remove it from the pool. - When a PlatformView starts to `applyMutator`, it removes current the maskView, insert the maskView to the pool. - When the above PlatformView needs to a maskView, it grabs one from the pool. fixes: https://github.com/flutter/flutter/issues/125620 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
parent
2752fe57eb
commit
259217c7aa
@ -18,8 +18,6 @@
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
|
||||
|
||||
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
|
||||
|
||||
@implementation UIView (FirstResponder)
|
||||
- (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
|
||||
if (self.isFirstResponder) {
|
||||
@ -447,6 +445,8 @@ void FlutterPlatformViewsController::ClipViewSetMaskView(UIView* clipView) {
|
||||
clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
|
||||
}
|
||||
|
||||
// This method is only called when the `embedded_view` needs to be re-composited at the current
|
||||
// frame. See: `CompositeWithParams` for details.
|
||||
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
|
||||
UIView* embedded_view,
|
||||
const SkRect& bounding_rect) {
|
||||
@ -461,12 +461,10 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
|
||||
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
|
||||
FML_DCHECK(!clipView.maskView ||
|
||||
[clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
|
||||
if (mask_view_pool_.get() == nil) {
|
||||
mask_view_pool_.reset([[FlutterClippingMaskViewPool alloc]
|
||||
initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
|
||||
if (clipView.maskView) {
|
||||
[mask_view_pool_.get() insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
|
||||
clipView.maskView = nil;
|
||||
}
|
||||
[mask_view_pool_.get() recycleMaskViews];
|
||||
clipView.maskView = nil;
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
auto iter = mutators_stack.Begin();
|
||||
while (iter != mutators_stack.End()) {
|
||||
@ -570,6 +568,14 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
|
||||
embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
|
||||
}
|
||||
|
||||
// Composite the PlatformView with `view_id`.
|
||||
//
|
||||
// Every frame, during the paint traversal of the layer tree, this method is called for all
|
||||
// the PlatformViews in `views_to_recomposite_`.
|
||||
//
|
||||
// Note that `views_to_recomposite_` does not represent all the views in the view hierarchy,
|
||||
// if a PlatformView does not change its composition parameter from last frame, it is not
|
||||
// included in the `views_to_recomposite_`.
|
||||
void FlutterPlatformViewsController::CompositeWithParams(int64_t view_id,
|
||||
const EmbeddedViewParams& params) {
|
||||
CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());
|
||||
|
||||
@ -2649,7 +2649,8 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
[[[FlutterClippingMaskViewPool alloc] initWithCapacity:2] autorelease];
|
||||
FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
|
||||
[pool recycleMaskViews];
|
||||
[pool insertViewToPoolIfNeeded:view1];
|
||||
[pool insertViewToPoolIfNeeded:view2];
|
||||
CGRect newRect = CGRectMake(0, 0, 10, 10);
|
||||
FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
|
||||
FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
|
||||
@ -2727,10 +2728,6 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack1);
|
||||
|
||||
flutter::MutatorsStack stack2;
|
||||
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(1);
|
||||
UIView* childClippingView1 = gMockPlatformView.superview.superview;
|
||||
@ -2738,6 +2735,10 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
XCTAssertNotNil(maskView1);
|
||||
|
||||
// Composite a new frame.
|
||||
flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100));
|
||||
flutter::MutatorsStack stack2;
|
||||
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3));
|
||||
@ -2763,6 +2764,77 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
XCTAssertNil(childClippingView1.maskView);
|
||||
}
|
||||
|
||||
- (void)testDifferentClipMaskViewIsUsedForEachView {
|
||||
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
|
||||
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
|
||||
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
|
||||
/*platform=*/thread_task_runner,
|
||||
/*raster=*/thread_task_runner,
|
||||
/*ui=*/thread_task_runner,
|
||||
/*io=*/thread_task_runner);
|
||||
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
|
||||
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
|
||||
/*delegate=*/mock_delegate,
|
||||
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners);
|
||||
|
||||
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
|
||||
flutterPlatformViewsController->RegisterViewFactory(
|
||||
factory, @"MockFlutterPlatformView",
|
||||
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
|
||||
FlutterResult result = ^(id result) {
|
||||
};
|
||||
|
||||
flutterPlatformViewsController->OnMethodCall(
|
||||
[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
|
||||
result);
|
||||
UIView* view1 = gMockPlatformView;
|
||||
|
||||
// This overwrites `gMockPlatformView` to another view.
|
||||
flutterPlatformViewsController->OnMethodCall(
|
||||
[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
|
||||
result);
|
||||
UIView* view2 = gMockPlatformView;
|
||||
|
||||
XCTAssertNotNil(gMockPlatformView);
|
||||
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
|
||||
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
|
||||
// Create embedded view params
|
||||
flutter::MutatorsStack stack1;
|
||||
// Layer tree always pushes a screen scale factor to the stack
|
||||
SkMatrix screenScaleMatrix =
|
||||
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
|
||||
stack1.PushTransform(screenScaleMatrix);
|
||||
// Push a clip rect
|
||||
SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
|
||||
stack1.PushClipRect(rect);
|
||||
|
||||
auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack1);
|
||||
|
||||
flutter::MutatorsStack stack2;
|
||||
stack2.PushClipRect(rect);
|
||||
auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
|
||||
screenScaleMatrix, SkSize::Make(10, 10), stack2);
|
||||
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(1);
|
||||
UIView* childClippingView1 = view1.superview.superview;
|
||||
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2));
|
||||
flutterPlatformViewsController->CompositeEmbeddedView(2);
|
||||
UIView* childClippingView2 = view2.superview.superview;
|
||||
UIView* maskView1 = childClippingView1.maskView;
|
||||
UIView* maskView2 = childClippingView2.maskView;
|
||||
XCTAssertNotEqual(maskView1, maskView2);
|
||||
}
|
||||
|
||||
// Return true if a correct visual effect view is found. It also implies all the validation in this
|
||||
// method passes.
|
||||
//
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
// in the pool. If there are none available, a new FlutterClippingMaskView is constructed. If the
|
||||
// capacity is reached, the newly constructed FlutterClippingMaskView is not added to the pool.
|
||||
//
|
||||
// Call |recycleMaskViews| to mark all the FlutterClippingMaskViews in the pool available.
|
||||
// Call |insertViewToPoolIfNeeded:| to return a maskView to the pool.
|
||||
@interface FlutterClippingMaskViewPool : NSObject
|
||||
|
||||
// Initialize the pool with `capacity`. When the `capacity` is reached, a FlutterClippingMaskView is
|
||||
@ -66,8 +66,8 @@
|
||||
// Reuse a maskView from the pool, or allocate a new one.
|
||||
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame;
|
||||
|
||||
// Mark all the maskViews available.
|
||||
- (void)recycleMaskViews;
|
||||
// Insert the `maskView` into the pool.
|
||||
- (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView;
|
||||
|
||||
@end
|
||||
|
||||
@ -291,20 +291,12 @@ class FlutterPlatformViewsController {
|
||||
int CountClips(const MutatorsStack& mutators_stack);
|
||||
|
||||
void ClipViewSetMaskView(UIView* clipView);
|
||||
|
||||
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
|
||||
// `ReconstructClipViewsChain`
|
||||
//
|
||||
// Clips are applied to the super view with a CALayer mask. Transforms are applied to the
|
||||
// current view that's at the head of the chain. For example the following mutators stack [T_1,
|
||||
// C_2, T_3, T_4, C_5, T_6] where T denotes a transform and C denotes a clip, will result in the
|
||||
// following UIView tree:
|
||||
//
|
||||
// C_2 -> C_5 -> PLATFORM_VIEW
|
||||
// (PLATFORM_VIEW is a subview of C_5 which is a subview of C_2)
|
||||
//
|
||||
// T_1 is applied to C_2, T_3 and T_4 are applied to C_5, and T_6 is applied to PLATFORM_VIEW.
|
||||
//
|
||||
// After each clip operation, we update the head to the super view of the current head.
|
||||
// Clips are applied to the `embedded_view`'s super view(|ChildClippingView|) using a
|
||||
// |FlutterClippingMaskView|. Transforms are applied to `embedded_view`
|
||||
//
|
||||
// The `bounding_rect` is the final bounding rect of the PlatformView
|
||||
// (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding
|
||||
@ -312,6 +304,7 @@ class FlutterPlatformViewsController {
|
||||
void ApplyMutators(const MutatorsStack& mutators_stack,
|
||||
UIView* embedded_view,
|
||||
const SkRect& bounding_rect);
|
||||
|
||||
void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params);
|
||||
|
||||
// Allocates a new FlutterPlatformViewLayer if needed, draws the pixels within the rect from
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
|
||||
|
||||
static int kMaxPointsInVerb = 4;
|
||||
static const NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
|
||||
|
||||
namespace flutter {
|
||||
|
||||
@ -26,7 +27,10 @@ FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default;
|
||||
|
||||
FlutterPlatformViewsController::FlutterPlatformViewsController()
|
||||
: layer_pool_(std::make_unique<FlutterPlatformViewLayerPool>()),
|
||||
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)){};
|
||||
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)) {
|
||||
mask_view_pool_.reset(
|
||||
[[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
|
||||
};
|
||||
|
||||
FlutterPlatformViewsController::~FlutterPlatformViewsController() = default;
|
||||
|
||||
@ -458,9 +462,10 @@ static BOOL _preparedOnce = NO;
|
||||
// The maximum number of `FlutterClippingMaskView` the pool can contain.
|
||||
// This prevents the pool to grow infinately and limits the maximum memory a pool can use.
|
||||
@property(assign, nonatomic) NSUInteger capacity;
|
||||
@property(retain, nonatomic) NSMutableArray<FlutterClippingMaskView*>* pool;
|
||||
// The index points to the first available FlutterClippingMaskView in the `pool`.
|
||||
@property(assign, nonatomic) NSUInteger availableIndex;
|
||||
|
||||
// The pool contains the views that are available to use.
|
||||
// The number of items in the pool must not excceds `capacity`.
|
||||
@property(retain, nonatomic) NSMutableSet<FlutterClippingMaskView*>* pool;
|
||||
|
||||
@end
|
||||
|
||||
@ -468,48 +473,42 @@ static BOOL _preparedOnce = NO;
|
||||
|
||||
- (instancetype)initWithCapacity:(NSInteger)capacity {
|
||||
if (self = [super init]) {
|
||||
_pool = [[NSMutableArray alloc] initWithCapacity:capacity];
|
||||
// Most of cases, there are only one PlatformView in the scene.
|
||||
// Thus init with the capacity of 1.
|
||||
_pool = [[NSMutableSet alloc] initWithCapacity:1];
|
||||
_capacity = capacity;
|
||||
_availableIndex = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame {
|
||||
FML_DCHECK(self.availableIndex <= self.capacity);
|
||||
FML_DCHECK(self.pool.count <= self.capacity);
|
||||
FlutterClippingMaskView* maskView;
|
||||
if (self.availableIndex == self.capacity) {
|
||||
// The pool is full, alloc a new one.
|
||||
if (self.pool.count == 0) {
|
||||
// The pool is empty, alloc a new one.
|
||||
maskView =
|
||||
[[[FlutterClippingMaskView alloc] initWithFrame:frame
|
||||
screenScale:[UIScreen mainScreen].scale] autorelease];
|
||||
return maskView;
|
||||
}
|
||||
|
||||
if (self.availableIndex >= self.pool.count) {
|
||||
// The pool doesn't have enough maskViews, alloc a new one and add to the pool.
|
||||
maskView =
|
||||
[[[FlutterClippingMaskView alloc] initWithFrame:frame
|
||||
screenScale:[UIScreen mainScreen].scale] autorelease];
|
||||
[self.pool addObject:maskView];
|
||||
FML_DCHECK(self.pool.count <= self.capacity);
|
||||
} else {
|
||||
// Reuse a maskView from the pool.
|
||||
maskView = [self.pool objectAtIndex:self.availableIndex];
|
||||
maskView.frame = frame;
|
||||
[maskView reset];
|
||||
}
|
||||
self.availableIndex++;
|
||||
maskView = [self.pool anyObject];
|
||||
maskView.frame = frame;
|
||||
[maskView reset];
|
||||
[self.pool removeObject:maskView];
|
||||
return maskView;
|
||||
}
|
||||
|
||||
- (void)recycleMaskViews {
|
||||
self.availableIndex = 0;
|
||||
- (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView {
|
||||
FML_DCHECK(![self.pool containsObject:maskView]);
|
||||
FML_DCHECK(self.pool.count <= self.capacity);
|
||||
if (self.pool.count == self.capacity) {
|
||||
return;
|
||||
}
|
||||
[self.pool addObject:maskView];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_pool release];
|
||||
_pool = nil;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@ -57,6 +57,9 @@
|
||||
684FFF8D29F9C10700281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 684FFF7929F9C10600281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png */; };
|
||||
684FFF8E29F9C10700281002 /* golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 684FFF7A29F9C10700281002 /* golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png */; };
|
||||
684FFF8F29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 684FFF7B29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png */; };
|
||||
6860CE252A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6860CE222A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png */; };
|
||||
6860CE262A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6860CE232A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png */; };
|
||||
6860CE272A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6860CE242A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png */; };
|
||||
68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */; };
|
||||
68D4017D2564859300ECD91A /* ContinuousTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = 68D4017C2564859300ECD91A /* ContinuousTexture.m */; };
|
||||
F26F15B8268B6B5600EC54D3 /* iPadGestureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F26F15B7268B6B5500EC54D3 /* iPadGestureTests.m */; };
|
||||
@ -174,6 +177,9 @@
|
||||
684FFF7929F9C10600281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
|
||||
684FFF7A29F9C10700281002 /* golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_spawn_engine_works_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
|
||||
684FFF7B29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
|
||||
6860CE222A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
|
||||
6860CE232A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
|
||||
6860CE242A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png"; sourceTree = "<group>"; };
|
||||
68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewGestureRecognizerTests.m; sourceTree = "<group>"; };
|
||||
68D4017B2564859300ECD91A /* ContinuousTexture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContinuousTexture.h; sourceTree = "<group>"; };
|
||||
68D4017C2564859300ECD91A /* ContinuousTexture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContinuousTexture.m; sourceTree = "<group>"; };
|
||||
@ -302,6 +308,9 @@
|
||||
F7B464DC2759D02B00079189 /* Goldens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6860CE242A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png */,
|
||||
6860CE232A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png */,
|
||||
6860CE222A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png */,
|
||||
684FFF7229F9C10500281002 /* golden_bogus_font_text_iPhone SE (3rd generation)_16.2_simulator.png */,
|
||||
684FFF7029F9C10500281002 /* golden_non_full_screen_flutter_view_platform_view_iPhone SE (3rd generation)_16.2_simulator.png */,
|
||||
684FFF7729F9C10600281002 /* golden_platform_view_clippath_iPhone SE (3rd generation)_16.2_simulator.png */,
|
||||
@ -456,7 +465,9 @@
|
||||
684FFF7D29F9C10700281002 /* golden_platform_view_cliprect_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8829F9C10700281002 /* golden_platform_view_cliprect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8529F9C10700281002 /* golden_platform_view_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
6860CE252A01B2FF00B68EC5 /* golden_two_platform_view_clip_rrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8F29F9C10700281002 /* golden_platform_view_with_other_backdrop_filter_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
6860CE262A01B2FF00B68EC5 /* golden_two_platform_view_clip_rect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8A29F9C10700281002 /* golden_platform_view_clippath_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8429F9C10700281002 /* golden_non_full_screen_flutter_view_platform_view_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF7C29F9C10700281002 /* golden_platform_view_cliprrect_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
@ -469,6 +480,7 @@
|
||||
684FFF8D29F9C10700281002 /* golden_platform_view_large_cliprrect_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8329F9C10700281002 /* golden_platform_view_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8B29F9C10700281002 /* golden_platform_view_clippath_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
6860CE272A01B2FF00B68EC5 /* golden_two_platform_view_clip_path_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
684FFF8129F9C10700281002 /* golden_platform_view_large_cliprrect_with_transform_iPhone SE (3rd generation)_16.2_simulator.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@ -69,7 +69,10 @@
|
||||
@"--spawn-engine-works" : @"spawn_engine_works",
|
||||
@"--pointer-events" : @"pointer_events",
|
||||
@"--platform-view-scrolling-under-widget" : @"platform_view_scrolling_under_widget",
|
||||
@"--platform-view-cliprect-after-moved" : @"platform_view_cliprect_after_moved"
|
||||
@"--platform-view-cliprect-after-moved" : @"platform_view_cliprect_after_moved",
|
||||
@"--two-platform-view-clip-rect" : @"two_platform_view_clip_rect",
|
||||
@"--two-platform-view-clip-rrect" : @"two_platform_view_clip_rrect",
|
||||
@"--two-platform-view-clip-path" : @"two_platform_view_clip_path",
|
||||
};
|
||||
__block NSString* flutterViewControllerTestName = nil;
|
||||
[launchArgsMap
|
||||
|
||||
@ -47,6 +47,9 @@ NSDictionary* launchArgsMap;
|
||||
@"--bogus-font-text" : @"bogus_font_text",
|
||||
@"--spawn-engine-works" : @"spawn_engine_works",
|
||||
@"--platform-view-cliprect-after-moved" : @"platform_view_cliprect_after_moved",
|
||||
@"--two-platform-view-clip-rect" : @"two_platform_view_clip_rect",
|
||||
@"--two-platform-view-clip-rrect" : @"two_platform_view_clip_rrect",
|
||||
@"--two-platform-view-clip-path" : @"two_platform_view_clip_path",
|
||||
};
|
||||
});
|
||||
_identifier = launchArgsMap[launchArg];
|
||||
|
||||
@ -254,6 +254,60 @@ static const NSInteger kSecondsToWaitForPlatformView = 30;
|
||||
|
||||
@end
|
||||
|
||||
@interface TwoPlatformViewClipRectTests : GoldenPlatformViewTests
|
||||
|
||||
@end
|
||||
|
||||
@implementation TwoPlatformViewClipRectTests
|
||||
|
||||
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
|
||||
GoldenTestManager* manager =
|
||||
[[GoldenTestManager alloc] initWithLaunchArg:@"--two-platform-view-clip-rect"];
|
||||
return [super initWithManager:manager invocation:invocation];
|
||||
}
|
||||
|
||||
- (void)testPlatformView {
|
||||
[self checkPlatformViewGolden];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TwoPlatformViewClipRRectTests : GoldenPlatformViewTests
|
||||
|
||||
@end
|
||||
|
||||
@implementation TwoPlatformViewClipRRectTests
|
||||
|
||||
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
|
||||
GoldenTestManager* manager =
|
||||
[[GoldenTestManager alloc] initWithLaunchArg:@"--two-platform-view-clip-rrect"];
|
||||
return [super initWithManager:manager invocation:invocation];
|
||||
}
|
||||
|
||||
- (void)testPlatformView {
|
||||
[self checkPlatformViewGolden];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TwoPlatformViewClipPathTests : GoldenPlatformViewTests
|
||||
|
||||
@end
|
||||
|
||||
@implementation TwoPlatformViewClipPathTests
|
||||
|
||||
- (instancetype)initWithInvocation:(NSInvocation*)invocation {
|
||||
GoldenTestManager* manager =
|
||||
[[GoldenTestManager alloc] initWithLaunchArg:@"--two-platform-view-clip-path"];
|
||||
return [super initWithManager:manager invocation:invocation];
|
||||
}
|
||||
|
||||
- (void)testPlatformView {
|
||||
[self checkPlatformViewGolden];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface PlatformViewMutationTransformTests : GoldenPlatformViewTests
|
||||
|
||||
@end
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@ -112,7 +112,6 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A platform view that is larger than the display size.
|
||||
/// This is only applicable on Android while using virtual displays.
|
||||
/// Related issue: https://github.com/flutter/flutter/issues/28978.
|
||||
@ -576,7 +575,7 @@ class PlatformViewClipRectAfterMovedScenario extends Scenario with _BasePlatform
|
||||
..pushClipRect(const Rect.fromLTRB(100, 100, 400, 400));
|
||||
|
||||
addPlatformView(
|
||||
_numberOfFrames == 10? 10000:id,
|
||||
_numberOfFrames == 10? 10000: id,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
);
|
||||
@ -639,7 +638,6 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Platform view with clip rrect.
|
||||
/// The bounding rect of the rrect is the same as PlatformView and only the corner radii clips the PlatformView.
|
||||
class PlatformViewLargeClipRRectScenario extends PlatformViewScenario {
|
||||
@ -878,6 +876,187 @@ class PlatformViewClipPathWithTransformScenario extends PlatformViewScenario {
|
||||
}
|
||||
}
|
||||
|
||||
/// Two platform views, both have clip rects
|
||||
class TwoPlatformViewClipRect extends Scenario
|
||||
with _BasePlatformViewScenarioMixin {
|
||||
/// Creates the PlatformView scenario.
|
||||
TwoPlatformViewClipRect(
|
||||
super.view, {
|
||||
required this.firstId,
|
||||
required this.secondId,
|
||||
});
|
||||
|
||||
/// The platform view identifier to use for the first platform view.
|
||||
final int firstId;
|
||||
|
||||
/// The platform view identifier to use for the second platform view.
|
||||
final int secondId;
|
||||
|
||||
@override
|
||||
void onBeginFrame(Duration duration) {
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
builder.pushOffset(0, 600);
|
||||
builder.pushClipRect(const Rect.fromLTRB(100, 100, 400, 400));
|
||||
|
||||
addPlatformView(
|
||||
firstId,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
text: 'platform view 1',
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
// Use a different rect to differentiate from the 1st clip rect.
|
||||
builder.pushClipRect(const Rect.fromLTRB(100, 100, 300, 300));
|
||||
|
||||
addPlatformView(
|
||||
secondId,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
text: 'platform view 2',
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
final Scene scene = builder.build();
|
||||
view.render(scene);
|
||||
scene.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Two platform views, both have clip rrects
|
||||
class TwoPlatformViewClipRRect extends Scenario
|
||||
with _BasePlatformViewScenarioMixin {
|
||||
/// Creates the PlatformView scenario.
|
||||
TwoPlatformViewClipRRect(
|
||||
super.view, {
|
||||
required this.firstId,
|
||||
required this.secondId,
|
||||
});
|
||||
|
||||
/// The platform view identifier to use for the first platform view.
|
||||
final int firstId;
|
||||
|
||||
/// The platform view identifier to use for the second platform view.
|
||||
final int secondId;
|
||||
|
||||
@override
|
||||
void onBeginFrame(Duration duration) {
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
builder.pushOffset(0, 600);
|
||||
builder.pushClipRRect(
|
||||
RRect.fromLTRBAndCorners(
|
||||
0,
|
||||
0,
|
||||
500,
|
||||
500,
|
||||
topLeft: const Radius.circular(15),
|
||||
topRight: const Radius.circular(50),
|
||||
bottomLeft: const Radius.circular(50),
|
||||
),
|
||||
);
|
||||
|
||||
addPlatformView(
|
||||
firstId,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
text: 'platform view 1',
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
// Use a different rrect to differentiate from the 1st clip rrect.
|
||||
builder.pushClipRRect(
|
||||
RRect.fromLTRBAndCorners(
|
||||
0,
|
||||
0,
|
||||
500,
|
||||
500,
|
||||
topLeft: const Radius.circular(100),
|
||||
topRight: const Radius.circular(50),
|
||||
bottomLeft: const Radius.circular(50),
|
||||
),
|
||||
);
|
||||
|
||||
addPlatformView(
|
||||
secondId,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
text: 'platform view 2',
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
final Scene scene = builder.build();
|
||||
view.render(scene);
|
||||
scene.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Two platform views, both have clip path
|
||||
class TwoPlatformViewClipPath extends Scenario
|
||||
with _BasePlatformViewScenarioMixin {
|
||||
/// Creates the PlatformView scenario.
|
||||
TwoPlatformViewClipPath(
|
||||
super.view, {
|
||||
required this.firstId,
|
||||
required this.secondId,
|
||||
});
|
||||
|
||||
/// The platform view identifier to use for the first platform view.
|
||||
final int firstId;
|
||||
|
||||
/// The platform view identifier to use for the second platform view.
|
||||
final int secondId;
|
||||
|
||||
@override
|
||||
void onBeginFrame(Duration duration) {
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
builder.pushOffset(0, 600);
|
||||
final Path path = Path()
|
||||
..moveTo(100, 100)
|
||||
..quadraticBezierTo(50, 250, 100, 400)
|
||||
..lineTo(350, 400)
|
||||
..cubicTo(400, 300, 300, 200, 350, 100)
|
||||
..close();
|
||||
|
||||
builder.pushClipPath(path);
|
||||
|
||||
addPlatformView(
|
||||
firstId,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
text: 'platform view 1',
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
// Use a different path to differentiate from the 1st clip path.
|
||||
final Path path2 = Path()
|
||||
..moveTo(100, 100)
|
||||
..quadraticBezierTo(100, 150, 100, 400)
|
||||
..lineTo(350, 350)
|
||||
..cubicTo(400, 300, 300, 200, 350, 200)
|
||||
..close();
|
||||
|
||||
builder.pushClipPath(path2);
|
||||
|
||||
addPlatformView(
|
||||
secondId,
|
||||
dispatcher: view.platformDispatcher,
|
||||
sceneBuilder: builder,
|
||||
text: 'platform view 2',
|
||||
);
|
||||
|
||||
builder.pop();
|
||||
final Scene scene = builder.build();
|
||||
view.render(scene);
|
||||
scene.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform view with transform.
|
||||
class PlatformViewTransformScenario extends PlatformViewScenario {
|
||||
/// Constructs a platform view with transform scenario.
|
||||
@ -1044,10 +1223,10 @@ class PlatformViewForOverlappingPlatformViewsScenario extends Scenario
|
||||
|
||||
/// Creates the PlatformViewForOverlappingPlatformViewsScenario.
|
||||
PlatformViewForOverlappingPlatformViewsScenario(
|
||||
super.view, {
|
||||
required this.foregroundId,
|
||||
required this.backgroundId,
|
||||
}) {
|
||||
super.view, {
|
||||
required this.foregroundId,
|
||||
required this.backgroundId,
|
||||
}) {
|
||||
_nextFrame = _firstFrame;
|
||||
}
|
||||
|
||||
@ -1151,7 +1330,7 @@ class PlatformViewForOverlappingPlatformViewsScenario extends Scenario
|
||||
view.platformDispatcher.sendPlatformMessage(
|
||||
'flutter/platform_views',
|
||||
message.buffer.asByteData(),
|
||||
(ByteData? response) {},
|
||||
(ByteData? response) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1360,7 +1539,7 @@ class PlatformViewScrollingUnderWidget extends Scenario
|
||||
super.view, {
|
||||
required int firstPlatformViewId,
|
||||
required int lastPlatformViewId,
|
||||
}) : _firstPlatformViewId = firstPlatformViewId,
|
||||
}) : _firstPlatformViewId = firstPlatformViewId,
|
||||
_lastPlatformViewId = lastPlatformViewId;
|
||||
|
||||
final int _firstPlatformViewId;
|
||||
@ -1450,7 +1629,6 @@ void addPlatformView(
|
||||
}
|
||||
|
||||
final String platformViewKey = '$viewType-$id';
|
||||
|
||||
if (_createdPlatformViews.containsKey(platformViewKey)) {
|
||||
addPlatformViewToSceneBuilder(
|
||||
id,
|
||||
@ -1473,7 +1651,6 @@ void addPlatformView(
|
||||
const int valueString = 7;
|
||||
const int valueUint8List = 8;
|
||||
const int valueMap = 13;
|
||||
|
||||
final Uint8List message = Uint8List.fromList(<int>[
|
||||
valueString,
|
||||
..._encodeString('create'),
|
||||
|
||||
@ -53,6 +53,9 @@ Map<String, _ScenarioFactory> _scenarios = <String, _ScenarioFactory>{
|
||||
'platform_view_gesture_reject_after_touches_ended': (FlutterView view) => PlatformViewForTouchIOSScenario(view, id: _viewId++, accept: false, rejectUntilTouchesEnded: true),
|
||||
'platform_view_gesture_accept_with_overlapping_platform_views': (FlutterView view) => PlatformViewForOverlappingPlatformViewsScenario(view, foregroundId: _viewId++, backgroundId: _viewId++),
|
||||
'platform_view_scrolling_under_widget':(FlutterView view) => PlatformViewScrollingUnderWidget(view, firstPlatformViewId: _viewId++, lastPlatformViewId: _viewId+=16),
|
||||
'two_platform_view_clip_rect': (FlutterView view) => TwoPlatformViewClipRect(view, firstId: _viewId++, secondId: _viewId++),
|
||||
'two_platform_view_clip_rrect': (FlutterView view) => TwoPlatformViewClipRRect(view, firstId: _viewId++, secondId: _viewId++),
|
||||
'two_platform_view_clip_path': (FlutterView view) => TwoPlatformViewClipPath(view, firstId: _viewId++, secondId: _viewId++),
|
||||
'tap_status_bar': (FlutterView view) => TouchesScenario(view),
|
||||
'initial_route_reply': (FlutterView view) => InitialRouteReply(view),
|
||||
'platform_view_with_continuous_texture': (FlutterView view) => PlatformViewWithContinuousTexture(view, id: _viewId++),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user