|
167 | 167 |
|
168 | 168 | touch_interceptors_[viewId] =
|
169 | 169 | fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
|
170 |
| - root_views_[viewId] = fml::scoped_nsobject<UIView>([touch_interceptor retain]); |
| 170 | + |
| 171 | + ChildClippingView* clipping_view = |
| 172 | + [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease]; |
| 173 | + [clipping_view addSubview:touch_interceptor]; |
| 174 | + root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]); |
171 | 175 |
|
172 | 176 | result(nil);
|
173 | 177 | }
|
|
317 | 321 | return clipCount;
|
318 | 322 | }
|
319 | 323 |
|
320 |
| -UIView* FlutterPlatformViewsController::ReconstructClipViewsChain(int number_of_clips, |
321 |
| - UIView* platform_view, |
322 |
| - UIView* head_clip_view) { |
323 |
| - NSInteger indexInFlutterView = -1; |
324 |
| - if (head_clip_view.superview) { |
325 |
| - // TODO(cyanglaz): potentially cache the index of oldPlatformViewRoot to make this a O(1). |
326 |
| - // https://github.com/flutter/flutter/issues/35023 |
327 |
| - indexInFlutterView = [flutter_view_.get().subviews indexOfObject:head_clip_view]; |
328 |
| - [head_clip_view removeFromSuperview]; |
329 |
| - } |
330 |
| - UIView* head = platform_view; |
331 |
| - int clipIndex = 0; |
332 |
| - // Re-use as much existing clip views as needed. |
333 |
| - while (head != head_clip_view && clipIndex < number_of_clips) { |
334 |
| - head = head.superview; |
335 |
| - clipIndex++; |
336 |
| - } |
337 |
| - // If there were not enough existing clip views, add more. |
338 |
| - while (clipIndex < number_of_clips) { |
339 |
| - ChildClippingView* clippingView = |
340 |
| - [[[ChildClippingView alloc] initWithFrame:flutter_view_.get().bounds] autorelease]; |
341 |
| - [clippingView addSubview:head]; |
342 |
| - head = clippingView; |
343 |
| - clipIndex++; |
344 |
| - } |
345 |
| - [head removeFromSuperview]; |
346 |
| - |
347 |
| - if (indexInFlutterView > -1) { |
348 |
| - // The chain was previously attached; attach it to the same position. |
349 |
| - [flutter_view_.get() insertSubview:head atIndex:indexInFlutterView]; |
350 |
| - } |
351 |
| - return head; |
352 |
| -} |
353 |
| - |
354 | 324 | void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
|
355 | 325 | UIView* embedded_view) {
|
356 | 326 | FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
|
357 |
| - UIView* head = embedded_view; |
358 |
| - ResetAnchor(head.layer); |
| 327 | + ResetAnchor(embedded_view.layer); |
| 328 | + ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview; |
359 | 329 |
|
360 |
| - std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom(); |
361 |
| - while (iter != mutators_stack.Top()) { |
| 330 | + // The UIKit frame is set based on the logical resolution instead of physical. |
| 331 | + // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). |
| 332 | + // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals |
| 333 | + // 500 points in UIKit. And until this point, we did all the calculation based on the flow |
| 334 | + // resolution. So we need to scale down to match UIKit's logical resolution. |
| 335 | + CGFloat screenScale = [UIScreen mainScreen].scale; |
| 336 | + CATransform3D finalTransform = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1); |
| 337 | + |
| 338 | + // Mask view needs to be full screen because we might draw platform view pixels outside of the |
| 339 | + // `ChildClippingView`. Since the mask view's frame will be based on the `clipView`'s coordinate |
| 340 | + // system, we need to convert the flutter_view's frame to the clipView's coordinate system. The |
| 341 | + // mask view is not displayed on the screen. |
| 342 | + CGRect maskViewFrame = [flutter_view_ convertRect:flutter_view_.get().frame toView:clipView]; |
| 343 | + FlutterClippingMaskView* maskView = |
| 344 | + [[[FlutterClippingMaskView alloc] initWithFrame:maskViewFrame] autorelease]; |
| 345 | + auto iter = mutators_stack.Begin(); |
| 346 | + while (iter != mutators_stack.End()) { |
362 | 347 | switch ((*iter)->GetType()) {
|
363 | 348 | case transform: {
|
364 | 349 | CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
|
365 |
| - head.layer.transform = CATransform3DConcat(head.layer.transform, transform); |
| 350 | + finalTransform = CATransform3DConcat(transform, finalTransform); |
366 | 351 | break;
|
367 | 352 | }
|
368 | 353 | case clip_rect:
|
| 354 | + [maskView clipRect:(*iter)->GetRect() matrix:finalTransform]; |
| 355 | + break; |
369 | 356 | case clip_rrect:
|
370 |
| - case clip_path: { |
371 |
| - ChildClippingView* clipView = (ChildClippingView*)head.superview; |
372 |
| - clipView.layer.transform = CATransform3DIdentity; |
373 |
| - [clipView setClip:(*iter)->GetType() |
374 |
| - rect:(*iter)->GetRect() |
375 |
| - rrect:(*iter)->GetRRect() |
376 |
| - path:(*iter)->GetPath()]; |
377 |
| - ResetAnchor(clipView.layer); |
378 |
| - head = clipView; |
| 357 | + [maskView clipRRect:(*iter)->GetRRect() matrix:finalTransform]; |
| 358 | + break; |
| 359 | + case clip_path: |
| 360 | + [maskView clipPath:(*iter)->GetPath() matrix:finalTransform]; |
379 | 361 | break;
|
380 |
| - } |
381 | 362 | case opacity:
|
382 | 363 | embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
|
383 | 364 | break;
|
384 | 365 | }
|
385 | 366 | ++iter;
|
386 | 367 | }
|
387 |
| - // Reverse scale based on screen scale. |
| 368 | + // Reverse the offset of the clipView. |
| 369 | + // The clipView's frame includes the final translate of the final transform matrix. |
| 370 | + // So we need to revese this translate so the platform view can layout at the correct offset. |
388 | 371 | //
|
389 |
| - // The UIKit frame is set based on the logical resolution instead of physical. |
390 |
| - // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). |
391 |
| - // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals |
392 |
| - // 500 points in UIKit. And until this point, we did all the calculation based on the flow |
393 |
| - // resolution. So we need to scale down to match UIKit's logical resolution. |
394 |
| - CGFloat screenScale = [UIScreen mainScreen].scale; |
395 |
| - head.layer.transform = CATransform3DConcat( |
396 |
| - head.layer.transform, CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1)); |
| 372 | + // Note that we don't apply this transform matrix the clippings because clippings happen on the |
| 373 | + // mask view, whose origin is alwasy (0,0) to the flutter_view. |
| 374 | + CATransform3D reverseTranslate = |
| 375 | + CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0); |
| 376 | + embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate); |
| 377 | + clipView.maskView = maskView; |
397 | 378 | }
|
398 | 379 |
|
399 | 380 | void FlutterPlatformViewsController::CompositeWithParams(int view_id,
|
|
406 | 387 | touchInterceptor.alpha = 1;
|
407 | 388 |
|
408 | 389 | const MutatorsStack& mutatorStack = params.mutatorsStack();
|
409 |
| - int currentClippingCount = CountClips(mutatorStack); |
410 |
| - int previousClippingCount = clip_count_[view_id]; |
411 |
| - if (currentClippingCount != previousClippingCount) { |
412 |
| - clip_count_[view_id] = currentClippingCount; |
413 |
| - // If we have a different clipping count in this frame, we need to reconstruct the |
414 |
| - // ClippingChildView chain to prepare for `ApplyMutators`. |
415 |
| - UIView* oldPlatformViewRoot = root_views_[view_id].get(); |
416 |
| - UIView* newPlatformViewRoot = |
417 |
| - ReconstructClipViewsChain(currentClippingCount, touchInterceptor, oldPlatformViewRoot); |
418 |
| - root_views_[view_id] = fml::scoped_nsobject<UIView>([newPlatformViewRoot retain]); |
419 |
| - } |
| 390 | + UIView* clippingView = root_views_[view_id].get(); |
| 391 | + // The frame of the clipping view should be the final bounding rect. |
| 392 | + // Because the translate matrix in the Mutator Stack also includes the offset, |
| 393 | + // when we apply the transforms matrix in |ApplyMutators|, we need |
| 394 | + // to remember to do a reverse translate. |
| 395 | + const SkRect& rect = params.finalBoundingRect(); |
| 396 | + CGFloat screenScale = [UIScreen mainScreen].scale; |
| 397 | + clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, |
| 398 | + rect.width() / screenScale, rect.height() / screenScale); |
420 | 399 | ApplyMutators(mutatorStack, touchInterceptor);
|
421 | 400 | }
|
422 | 401 |
|
|
0 commit comments