• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
6
7#include "flutter/shell/platform/darwin/ios/ios_surface.h"
8
9static int kMaxPointsInVerb = 4;
10
11namespace flutter {
12
13FlutterPlatformViewLayer::FlutterPlatformViewLayer(fml::scoped_nsobject<UIView> overlay_view,
14                                                   std::unique_ptr<IOSSurface> ios_surface,
15                                                   std::unique_ptr<Surface> surface)
16    : overlay_view(std::move(overlay_view)),
17      ios_surface(std::move(ios_surface)),
18      surface(std::move(surface)){};
19
20FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default;
21
22FlutterPlatformViewsController::FlutterPlatformViewsController() = default;
23
24FlutterPlatformViewsController::~FlutterPlatformViewsController() = default;
25
26CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) {
27  // Skia only supports 2D transform so we don't map z.
28  CATransform3D transform = CATransform3DIdentity;
29  transform.m11 = matrix.getScaleX();
30  transform.m21 = matrix.getSkewX();
31  transform.m41 = matrix.getTranslateX();
32  transform.m14 = matrix.getPerspX();
33
34  transform.m12 = matrix.getSkewY();
35  transform.m22 = matrix.getScaleY();
36  transform.m42 = matrix.getTranslateY();
37  transform.m24 = matrix.getPerspY();
38  return transform;
39}
40
41void ResetAnchor(CALayer* layer) {
42  // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz.
43  layer.anchorPoint = CGPointZero;
44  layer.position = CGPointZero;
45}
46
47}  // namespace flutter
48
49@implementation ChildClippingView
50
51+ (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect {
52  return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
53                    clipSkRect.fBottom - clipSkRect.fTop);
54}
55
56- (void)clipRect:(const SkRect&)clipSkRect {
57  CGRect clipRect = [ChildClippingView getCGRectFromSkRect:clipSkRect];
58  CGPathRef pathRef = CGPathCreateWithRect(clipRect, nil);
59  CAShapeLayer* clip = [[CAShapeLayer alloc] init];
60  clip.path = pathRef;
61  self.layer.mask = clip;
62  CGPathRelease(pathRef);
63}
64
65- (void)clipRRect:(const SkRRect&)clipSkRRect {
66  CGPathRef pathRef = nullptr;
67  switch (clipSkRRect.getType()) {
68    case SkRRect::kEmpty_Type: {
69      break;
70    }
71    case SkRRect::kRect_Type: {
72      [self clipRect:clipSkRRect.rect()];
73      return;
74    }
75    case SkRRect::kOval_Type:
76    case SkRRect::kSimple_Type: {
77      CGRect clipRect = [ChildClippingView getCGRectFromSkRect:clipSkRRect.rect()];
78      pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(),
79                                            clipSkRRect.getSimpleRadii().y(), nil);
80      break;
81    }
82    case SkRRect::kNinePatch_Type:
83    case SkRRect::kComplex_Type: {
84      CGMutablePathRef mutablePathRef = CGPathCreateMutable();
85      // Complex types, we manually add each corner.
86      SkRect clipSkRect = clipSkRRect.rect();
87      SkVector topLeftRadii = clipSkRRect.radii(SkRRect::kUpperLeft_Corner);
88      SkVector topRightRadii = clipSkRRect.radii(SkRRect::kUpperRight_Corner);
89      SkVector bottomRightRadii = clipSkRRect.radii(SkRRect::kLowerRight_Corner);
90      SkVector bottomLeftRadii = clipSkRRect.radii(SkRRect::kLowerLeft_Corner);
91
92      // Start drawing RRect
93      // Move point to the top left corner adding the top left radii's x.
94      CGPathMoveToPoint(mutablePathRef, nil, clipSkRect.fLeft + topLeftRadii.x(), clipSkRect.fTop);
95      // Move point horizontally right to the top right corner and add the top right curve.
96      CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fRight - topRightRadii.x(),
97                           clipSkRect.fTop);
98      CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fRight, clipSkRect.fTop,
99                            clipSkRect.fRight, clipSkRect.fTop + topRightRadii.y(),
100                            clipSkRect.fRight, clipSkRect.fTop + topRightRadii.y());
101      // Move point vertically down to the bottom right corner and add the bottom right curve.
102      CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fRight,
103                           clipSkRect.fBottom - bottomRightRadii.y());
104      CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fRight, clipSkRect.fBottom,
105                            clipSkRect.fRight - bottomRightRadii.x(), clipSkRect.fBottom,
106                            clipSkRect.fRight - bottomRightRadii.x(), clipSkRect.fBottom);
107      // Move point horizontally left to the bottom left corner and add the bottom left curve.
108      CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fLeft + bottomLeftRadii.x(),
109                           clipSkRect.fBottom);
110      CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fLeft, clipSkRect.fBottom,
111                            clipSkRect.fLeft, clipSkRect.fBottom - bottomLeftRadii.y(),
112                            clipSkRect.fLeft, clipSkRect.fBottom - bottomLeftRadii.y());
113      // Move point vertically up to the top left corner and add the top left curve.
114      CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fLeft,
115                           clipSkRect.fTop + topLeftRadii.y());
116      CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fLeft, clipSkRect.fTop,
117                            clipSkRect.fLeft + topLeftRadii.x(), clipSkRect.fTop,
118                            clipSkRect.fLeft + topLeftRadii.x(), clipSkRect.fTop);
119      CGPathCloseSubpath(mutablePathRef);
120
121      pathRef = mutablePathRef;
122      break;
123    }
124  }
125  // TODO(cyanglaz): iOS does not seem to support hard edge on CAShapeLayer. It clearly stated that
126  // the CAShaperLayer will be drawn antialiased. Need to figure out a way to do the hard edge
127  // clipping on iOS.
128  CAShapeLayer* clip = [[CAShapeLayer alloc] init];
129  clip.path = pathRef;
130  self.layer.mask = clip;
131  CGPathRelease(pathRef);
132}
133
134- (void)clipPath:(const SkPath&)path {
135  CGMutablePathRef pathRef = CGPathCreateMutable();
136  if (!path.isValid()) {
137    return;
138  }
139  if (path.isEmpty()) {
140    CAShapeLayer* clip = [[CAShapeLayer alloc] init];
141    clip.path = pathRef;
142    self.layer.mask = clip;
143    CGPathRelease(pathRef);
144    return;
145  }
146
147  // Loop through all verbs and translate them into CGPath
148  SkPath::Iter iter(path, true);
149  SkPoint pts[kMaxPointsInVerb];
150  SkPath::Verb verb = iter.next(pts);
151  SkPoint last_pt_from_last_verb;
152  while (verb != SkPath::kDone_Verb) {
153    if (verb == SkPath::kLine_Verb || verb == SkPath::kQuad_Verb || verb == SkPath::kConic_Verb ||
154        verb == SkPath::kCubic_Verb) {
155      FML_DCHECK(last_pt_from_last_verb == pts[0]);
156    }
157    switch (verb) {
158      case SkPath::kMove_Verb: {
159        CGPathMoveToPoint(pathRef, nil, pts[0].x(), pts[0].y());
160        last_pt_from_last_verb = pts[0];
161        break;
162      }
163      case SkPath::kLine_Verb: {
164        CGPathAddLineToPoint(pathRef, nil, pts[1].x(), pts[1].y());
165        last_pt_from_last_verb = pts[1];
166        break;
167      }
168      case SkPath::kQuad_Verb: {
169        CGPathAddQuadCurveToPoint(pathRef, nil, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
170        last_pt_from_last_verb = pts[2];
171        break;
172      }
173      case SkPath::kConic_Verb: {
174        // Conic is not available in quartz, we use quad to approximate.
175        // TODO(cyanglaz): Better approximate the conic path.
176        // https://github.com/flutter/flutter/issues/35062
177        CGPathAddQuadCurveToPoint(pathRef, nil, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
178        last_pt_from_last_verb = pts[2];
179        break;
180      }
181      case SkPath::kCubic_Verb: {
182        CGPathAddCurveToPoint(pathRef, nil, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
183                              pts[3].x(), pts[3].y());
184        last_pt_from_last_verb = pts[3];
185        break;
186      }
187      case SkPath::kClose_Verb: {
188        CGPathCloseSubpath(pathRef);
189        break;
190      }
191      case SkPath::kDone_Verb: {
192        break;
193      }
194    }
195    verb = iter.next(pts);
196  }
197
198  CAShapeLayer* clip = [[CAShapeLayer alloc] init];
199  clip.path = pathRef;
200  self.layer.mask = clip;
201  CGPathRelease(pathRef);
202}
203
204- (void)setClip:(flutter::MutatorType)type
205           rect:(const SkRect&)rect
206          rrect:(const SkRRect&)rrect
207           path:(const SkPath&)path {
208  FML_CHECK(type == flutter::clip_rect || type == flutter::clip_rrect ||
209            type == flutter::clip_path);
210  switch (type) {
211    case flutter::clip_rect:
212      [self clipRect:rect];
213      break;
214    case flutter::clip_rrect:
215      [self clipRRect:rrect];
216      break;
217    case flutter::clip_path:
218      [self clipPath:path];
219      break;
220    default:
221      break;
222  }
223}
224
225// The ChildClippingView is as big as the FlutterView, we only want touches to be hit tested and
226// consumed by this view if they are inside the smaller child view.
227- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
228  for (UIView* view in self.subviews) {
229    if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
230      return YES;
231    }
232  }
233  return NO;
234}
235
236@end
237