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