react-native-smooth-clip-view 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +60 -21
  2. package/android/build.gradle +6 -2
  3. package/android/proguard-rules.pro +3 -3
  4. package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
  5. package/android/src/main/cpp/SmoothClipBindings.cpp +890 -4
  6. package/android/src/main/cpp/SmoothClipRegistry.cpp +1056 -44
  7. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +265 -0
  8. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +21 -0
  9. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +283 -0
  10. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +371 -18
  11. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
  12. package/cpp/SmoothClipAnimationCurve.h +151 -122
  13. package/cpp/SmoothClipRegistry.h +95 -3
  14. package/cpp/SmoothClipSharedGeometry.h +146 -7
  15. package/cpp/SmoothClipVelocityTracker.h +13 -9
  16. package/ios/SmoothClipGeometry.h +184 -6
  17. package/ios/SmoothClipRegistry.mm +1028 -22
  18. package/ios/SmoothClipRegistrySnapshot.h +13 -0
  19. package/ios/SmoothClipTurboModule.cpp +857 -2
  20. package/ios/SmoothClipTurboModule.h +310 -0
  21. package/ios/SmoothClipView.mm +714 -183
  22. package/ios/SmoothClipViewRegistry.h +1 -0
  23. package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  24. package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  25. package/lib/module/NativeSmoothClipModule.js.map +1 -1
  26. package/lib/module/SmoothClipView.ios.js +22 -6
  27. package/lib/module/SmoothClipView.ios.js.map +1 -1
  28. package/lib/module/SmoothClipView.js +1 -46
  29. package/lib/module/SmoothClipView.js.map +1 -1
  30. package/lib/module/SmoothClipViewNativeComponent.ts +27 -1
  31. package/lib/module/capabilities.js +4 -0
  32. package/lib/module/capabilities.js.map +1 -0
  33. package/lib/module/capabilities.native.js +36 -0
  34. package/lib/module/capabilities.native.js.map +1 -0
  35. package/lib/module/capabilityTypes.js +11 -0
  36. package/lib/module/capabilityTypes.js.map +1 -0
  37. package/lib/module/driverState.js +0 -28
  38. package/lib/module/driverState.js.map +1 -1
  39. package/lib/module/drivers.js +1 -281
  40. package/lib/module/drivers.js.map +1 -1
  41. package/lib/module/drivers.native.js +263 -63
  42. package/lib/module/drivers.native.js.map +1 -1
  43. package/lib/module/geometry.js +147 -17
  44. package/lib/module/geometry.js.map +1 -1
  45. package/lib/module/groupDriverTypes.js +4 -0
  46. package/lib/module/groupDriverTypes.js.map +1 -0
  47. package/lib/module/groupDrivers.android.js +4 -0
  48. package/lib/module/groupDrivers.android.js.map +1 -0
  49. package/lib/module/groupDrivers.ios.js +4 -0
  50. package/lib/module/groupDrivers.ios.js.map +1 -0
  51. package/lib/module/groupDrivers.js +4 -0
  52. package/lib/module/groupDrivers.js.map +1 -0
  53. package/lib/module/groupDrivers.native.js +799 -0
  54. package/lib/module/groupDrivers.native.js.map +1 -0
  55. package/lib/module/index.js +3 -2
  56. package/lib/module/index.js.map +1 -1
  57. package/lib/module/presentationProtocol.js +15 -0
  58. package/lib/module/presentationProtocol.js.map +1 -0
  59. package/lib/typescript/src/NativeSmoothClipModule.d.ts +25 -0
  60. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  61. package/lib/typescript/src/SmoothClipView.d.ts +2 -10
  62. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  63. package/lib/typescript/src/SmoothClipView.ios.d.ts +133 -2
  64. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  65. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +8 -0
  66. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  67. package/lib/typescript/src/capabilities.d.ts +3 -0
  68. package/lib/typescript/src/capabilities.d.ts.map +1 -0
  69. package/lib/typescript/src/capabilities.native.d.ts +3 -0
  70. package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
  71. package/lib/typescript/src/capabilityTypes.d.ts +10 -0
  72. package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
  73. package/lib/typescript/src/driverState.d.ts +1 -6
  74. package/lib/typescript/src/driverState.d.ts.map +1 -1
  75. package/lib/typescript/src/driverTypes.d.ts +25 -11
  76. package/lib/typescript/src/driverTypes.d.ts.map +1 -1
  77. package/lib/typescript/src/drivers.d.ts +2 -4
  78. package/lib/typescript/src/drivers.d.ts.map +1 -1
  79. package/lib/typescript/src/drivers.native.d.ts.map +1 -1
  80. package/lib/typescript/src/geometry.d.ts +36 -2
  81. package/lib/typescript/src/geometry.d.ts.map +1 -1
  82. package/lib/typescript/src/groupDriverTypes.d.ts +66 -0
  83. package/lib/typescript/src/groupDriverTypes.d.ts.map +1 -0
  84. package/lib/typescript/src/groupDrivers.android.d.ts +2 -0
  85. package/lib/typescript/src/groupDrivers.android.d.ts.map +1 -0
  86. package/lib/typescript/src/groupDrivers.d.ts +2 -0
  87. package/lib/typescript/src/groupDrivers.d.ts.map +1 -0
  88. package/lib/typescript/src/groupDrivers.ios.d.ts +2 -0
  89. package/lib/typescript/src/groupDrivers.ios.d.ts.map +1 -0
  90. package/lib/typescript/src/groupDrivers.native.d.ts +4 -0
  91. package/lib/typescript/src/groupDrivers.native.d.ts.map +1 -0
  92. package/lib/typescript/src/index.d.ts +6 -3
  93. package/lib/typescript/src/index.d.ts.map +1 -1
  94. package/lib/typescript/src/presentationProtocol.d.ts +4 -0
  95. package/lib/typescript/src/presentationProtocol.d.ts.map +1 -0
  96. package/package.json +10 -11
  97. package/src/NativeSmoothClipModule.ts +275 -0
  98. package/src/SmoothClipView.ios.tsx +30 -11
  99. package/src/SmoothClipView.tsx +2 -76
  100. package/src/SmoothClipViewNativeComponent.ts +27 -1
  101. package/src/capabilities.native.ts +69 -0
  102. package/src/capabilities.ts +2 -0
  103. package/src/capabilityTypes.ts +17 -0
  104. package/src/driverState.ts +1 -41
  105. package/src/driverTypes.ts +42 -11
  106. package/src/drivers.native.ts +824 -124
  107. package/src/drivers.ts +2 -423
  108. package/src/geometry.ts +231 -27
  109. package/src/groupDriverTypes.ts +117 -0
  110. package/src/groupDrivers.android.ts +1 -0
  111. package/src/groupDrivers.ios.ts +1 -0
  112. package/src/groupDrivers.native.ts +1189 -0
  113. package/src/groupDrivers.ts +1 -0
  114. package/src/index.ts +30 -2
  115. package/src/presentationProtocol.ts +28 -0
  116. package/lib/module/SmoothClipView.web.js +0 -54
  117. package/lib/module/SmoothClipView.web.js.map +0 -1
  118. package/lib/module/index.web.js +0 -8
  119. package/lib/module/index.web.js.map +0 -1
  120. package/lib/module/webClipPath.js +0 -25
  121. package/lib/module/webClipPath.js.map +0 -1
  122. package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
  123. package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
  124. package/lib/typescript/src/index.web.d.ts +0 -9
  125. package/lib/typescript/src/index.web.d.ts.map +0 -1
  126. package/lib/typescript/src/webClipPath.d.ts +0 -7
  127. package/lib/typescript/src/webClipPath.d.ts.map +0 -1
  128. package/src/SmoothClipView.web.tsx +0 -66
  129. package/src/index.web.ts +0 -23
  130. package/src/webClipPath.ts +0 -30
@@ -51,26 +51,164 @@ using namespace facebook::react;
51
51
  finished:(BOOL)finished;
52
52
  @end
53
53
 
54
- static std::array<double, 7> SmoothClipVelocityChannels(
55
- CGRect rect, CGFloat radius, CGPoint contentTranslation) {
54
+ static SmoothClipCornerRadii SmoothClipPresentationRadii(
55
+ const smoothclip::Geometry &geometry) {
56
+ return {
57
+ (CGFloat)smoothclip::resolvedRadius(
58
+ geometry.topLeftRadius, geometry.radius),
59
+ (CGFloat)smoothclip::resolvedRadius(
60
+ geometry.topRightRadius, geometry.radius),
61
+ (CGFloat)smoothclip::resolvedRadius(
62
+ geometry.bottomRightRadius, geometry.radius),
63
+ (CGFloat)smoothclip::resolvedRadius(
64
+ geometry.bottomLeftRadius, geometry.radius),
65
+ };
66
+ }
67
+
68
+ static BOOL SmoothClipPresentationHasExplicitRadii(
69
+ const smoothclip::Geometry &geometry) {
70
+ return isfinite(geometry.topLeftRadius) ||
71
+ isfinite(geometry.topRightRadius) ||
72
+ isfinite(geometry.bottomRightRadius) ||
73
+ isfinite(geometry.bottomLeftRadius);
74
+ }
75
+
76
+ static SmoothClipCornerCurve SmoothClipPresentationCurve(
77
+ const smoothclip::Geometry &geometry) {
78
+ return geometry.curve == smoothclip::ClipCurve::Continuous
79
+ ? SmoothClipCornerCurveContinuous
80
+ : SmoothClipCornerCurveCircular;
81
+ }
82
+
83
+ static bool SmoothClipBuildPresentationV2(
84
+ double x,
85
+ double y,
86
+ double width,
87
+ double height,
88
+ double topLeftRadius,
89
+ double topRightRadius,
90
+ double bottomRightRadius,
91
+ double bottomLeftRadius,
92
+ NSInteger curveCode,
93
+ double contentTranslateX,
94
+ double contentTranslateY,
95
+ double contentScale,
96
+ smoothclip::Presentation *result) {
97
+ if (result == nullptr || !isfinite(x) || !isfinite(y) ||
98
+ !isfinite(width) || !isfinite(height) ||
99
+ !isfinite(topLeftRadius) || !isfinite(topRightRadius) ||
100
+ !isfinite(bottomRightRadius) || !isfinite(bottomLeftRadius) ||
101
+ !isfinite(contentTranslateX) || !isfinite(contentTranslateY) ||
102
+ !isfinite(contentScale) || contentScale <= 0 ||
103
+ (curveCode != static_cast<NSInteger>(smoothclip::ClipCurve::Circular) &&
104
+ curveCode != static_cast<NSInteger>(smoothclip::ClipCurve::Continuous))) {
105
+ return false;
106
+ }
107
+
108
+ const bool uniform = topLeftRadius == topRightRadius &&
109
+ topLeftRadius == bottomRightRadius &&
110
+ topLeftRadius == bottomLeftRadius;
111
+ smoothclip::Geometry geometry{
112
+ x, y, width, height, uniform ? topLeftRadius : 0};
113
+ geometry.topLeftRadius = topLeftRadius;
114
+ geometry.topRightRadius = topRightRadius;
115
+ geometry.bottomRightRadius = bottomRightRadius;
116
+ geometry.bottomLeftRadius = bottomLeftRadius;
117
+ geometry.curve = static_cast<smoothclip::ClipCurve>(curveCode);
118
+ *result = {
119
+ geometry, contentTranslateX, contentTranslateY, contentScale};
120
+ return true;
121
+ }
122
+
123
+ static NSString *SmoothClipCALayerCornerCurve(
124
+ SmoothClipCornerCurve curve) API_AVAILABLE(ios(13.0)) {
125
+ return curve == SmoothClipCornerCurveContinuous
126
+ ? kCACornerCurveContinuous
127
+ : kCACornerCurveCircular;
128
+ }
129
+
130
+ static CGAffineTransform SmoothClipContentTransform(
131
+ CGFloat scale,
132
+ CGPoint translation) {
133
+ // Construct the matrix directly. Concatenating a translation and a scale is
134
+ // order-sensitive and can multiply tx/ty; the presentation contract requires
135
+ // a centered uniform scale with translation remaining in unscaled points.
136
+ return CGAffineTransformMake(
137
+ scale, 0, 0, scale, translation.x, translation.y);
138
+ }
139
+
140
+ struct SmoothClipPathEndpoints {
141
+ CGPoint points[9];
142
+ size_t count;
143
+ };
144
+
145
+ static void SmoothClipCollectPathEndpoints(
146
+ void *context,
147
+ const CGPathElement *element) {
148
+ SmoothClipPathEndpoints *endpoints =
149
+ static_cast<SmoothClipPathEndpoints *>(context);
150
+ if (endpoints->count >= 9 ||
151
+ element->type == kCGPathElementCloseSubpath) {
152
+ return;
153
+ }
154
+ const size_t endpointIndex =
155
+ element->type == kCGPathElementAddCurveToPoint ? 2 :
156
+ element->type == kCGPathElementAddQuadCurveToPoint ? 1 : 0;
157
+ endpoints->points[endpoints->count++] = element->points[endpointIndex];
158
+ }
159
+
160
+ static SmoothClipCornerRadii SmoothClipRadiiFromFixedPath(
161
+ CGPathRef path,
162
+ CGRect rect,
163
+ SmoothClipCornerRadii fallback) {
164
+ if (path == nil) return fallback;
165
+ SmoothClipPathEndpoints endpoints{};
166
+ CGPathApply(path, &endpoints, SmoothClipCollectPathEndpoints);
167
+ if (endpoints.count != 9) return fallback;
168
+ return {
169
+ MAX(0, endpoints.points[0].x - CGRectGetMinX(rect)),
170
+ MAX(0, CGRectGetMaxX(rect) - endpoints.points[1].x),
171
+ MAX(0, CGRectGetMaxY(rect) - endpoints.points[3].y),
172
+ MAX(0, endpoints.points[5].x - CGRectGetMinX(rect)),
173
+ };
174
+ }
175
+
176
+ static std::array<double, 11> SmoothClipVelocityChannels(
177
+ CGRect rect,
178
+ SmoothClipCornerRadii radii,
179
+ CGPoint contentTranslation,
180
+ CGFloat contentScale) {
56
181
  return {CGRectGetMinX(rect),
57
182
  CGRectGetMinY(rect),
58
183
  CGRectGetWidth(rect),
59
184
  CGRectGetHeight(rect),
60
- radius,
185
+ radii.topLeft,
186
+ radii.topRight,
187
+ radii.bottomRight,
188
+ radii.bottomLeft,
61
189
  contentTranslation.x,
62
- contentTranslation.y};
190
+ contentTranslation.y,
191
+ contentScale};
63
192
  }
64
193
 
65
194
  @implementation SmoothClipView {
66
195
  SmoothClipContainerView *_clipContainer;
67
196
  SmoothClipContainerView *_contentContainer;
197
+ CAShapeLayer *_unequalCornerMask;
68
198
  CGRect _requestedClip;
69
199
  CGRect _normalizedClip;
70
200
  CGFloat _requestedRadius;
71
201
  CGFloat _normalizedRadius;
202
+ SmoothClipCornerRadii _requestedRadii;
203
+ SmoothClipCornerRadii _normalizedRadii;
204
+ SmoothClipCornerCurve _requestedCurve;
205
+ SmoothClipCornerCurve _normalizedCurve;
206
+ BOOL _requestedHasExplicitRadii;
207
+ BOOL _normalizedHasExplicitRadii;
72
208
  CGPoint _requestedContentTranslation;
73
209
  CGPoint _normalizedContentTranslation;
210
+ CGFloat _requestedContentScale;
211
+ CGFloat _normalizedContentScale;
74
212
  uint64_t _driverId;
75
213
  BOOL _hasLayout;
76
214
  BOOL _commandIsAuthoritative;
@@ -139,12 +277,29 @@ static std::array<double, 7> SmoothClipVelocityChannels(
139
277
  [_clipContainer addSubview:_contentContainer];
140
278
  [self addSubview:_clipContainer];
141
279
 
280
+ _unequalCornerMask = [CAShapeLayer layer];
281
+ _unequalCornerMask.actions = @{
282
+ @"path": [NSNull null],
283
+ @"bounds": [NSNull null],
284
+ @"position": [NSNull null],
285
+ };
286
+ _unequalCornerMask.fillColor = UIColor.blackColor.CGColor;
287
+ _unequalCornerMask.needsDisplayOnBoundsChange = NO;
288
+
142
289
  _requestedClip = CGRectZero;
143
290
  _normalizedClip = CGRectZero;
144
291
  _requestedRadius = 0;
145
292
  _normalizedRadius = 0;
293
+ _requestedRadii = {0, 0, 0, 0};
294
+ _normalizedRadii = {0, 0, 0, 0};
295
+ _requestedCurve = SmoothClipCornerCurveCircular;
296
+ _normalizedCurve = SmoothClipCornerCurveCircular;
297
+ _requestedHasExplicitRadii = NO;
298
+ _normalizedHasExplicitRadii = NO;
146
299
  _requestedContentTranslation = CGPointZero;
147
300
  _normalizedContentTranslation = CGPointZero;
301
+ _requestedContentScale = 1;
302
+ _normalizedContentScale = 1;
148
303
  _driverId = 0;
149
304
  _hasLayout = NO;
150
305
  _commandIsAuthoritative = NO;
@@ -198,10 +353,17 @@ static std::array<double, 7> SmoothClipVelocityChannels(
198
353
  geometry.y,
199
354
  MAX(0, geometry.width),
200
355
  MAX(0, geometry.height));
201
- _requestedRadius = MAX(0, geometry.radius);
356
+ _requestedRadii = SmoothClipPresentationRadii(geometry);
357
+ _requestedRadius = SmoothClipCornerRadiiAreUniform(_requestedRadii)
358
+ ? MAX(0, _requestedRadii.topLeft)
359
+ : 0;
360
+ _requestedCurve = SmoothClipPresentationCurve(geometry);
361
+ _requestedHasExplicitRadii =
362
+ SmoothClipPresentationHasExplicitRadii(geometry);
202
363
  _requestedContentTranslation = CGPointMake(
203
364
  presentation.contentTranslateX,
204
365
  presentation.contentTranslateY);
366
+ _requestedContentScale = presentation.contentScale;
205
367
  }
206
368
 
207
369
  - (BOOL)normalizedRequestedGeometry:(SmoothNormalizedClipGeometry *)geometry {
@@ -217,12 +379,16 @@ static std::array<double, 7> SmoothClipVelocityChannels(
217
379
  }
218
380
  #endif
219
381
  const CGSize hostSize = self.bounds.size;
220
- const BOOL valid = SmoothClipNormalizeGeometry(
382
+ const BOOL valid = SmoothClipNormalizeGeometryV2(
221
383
  CGRectGetMinX(_requestedClip),
222
384
  CGRectGetMinY(_requestedClip),
223
385
  CGRectGetWidth(_requestedClip),
224
386
  CGRectGetHeight(_requestedClip),
225
- _requestedRadius,
387
+ _requestedRadii.topLeft,
388
+ _requestedRadii.topRight,
389
+ _requestedRadii.bottomRight,
390
+ _requestedRadii.bottomLeft,
391
+ _requestedCurve,
226
392
  CGSizeMake(MAX(0, hostSize.width), MAX(0, hostSize.height)),
227
393
  geometry);
228
394
  #if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
@@ -245,12 +411,61 @@ static std::array<double, 7> SmoothClipVelocityChannels(
245
411
  [self setClipContainerHidden:CGRectIsEmpty(rect)];
246
412
  }
247
413
 
248
- - (void)writeLayerRect:(CGRect)rect radius:(CGFloat)radius {
249
- const BOOL rectChanged = !CGRectEqualToRect(_normalizedClip, rect);
250
- const BOOL radiusChanged = _normalizedRadius != radius;
251
- _normalizedClip = rect;
252
- _normalizedRadius = radius;
253
- if (!rectChanged && !radiusChanged) return;
414
+ - (void)configureUnequalCornerMaskForGeometry:
415
+ (SmoothNormalizedClipGeometry)geometry {
416
+ const CGSize hostSize = self.bounds.size;
417
+ _unequalCornerMask.bounds = CGRectMake(
418
+ 0, 0, MAX(0, hostSize.width), MAX(0, hostSize.height));
419
+ _unequalCornerMask.position = CGPointMake(
420
+ MAX(0, hostSize.width) / 2, MAX(0, hostSize.height) / 2);
421
+ CGPathRef path = SmoothClipCreateRoundedRectPath(
422
+ geometry.rect, geometry.radii, geometry.curve);
423
+ _unequalCornerMask.path = path;
424
+ CGPathRelease(path);
425
+ }
426
+
427
+ - (void)applyStaticCornerRepresentation:
428
+ (SmoothNormalizedClipGeometry)geometry {
429
+ CALayer *layer = _clipContainer.layer;
430
+ if (SmoothClipCornerRadiiAreUniform(geometry.radii)) {
431
+ layer.mask = nil;
432
+ layer.cornerRadius = geometry.radii.topLeft;
433
+ layer.cornerCurve = SmoothClipCALayerCornerCurve(geometry.curve);
434
+ } else {
435
+ layer.cornerRadius = 0;
436
+ layer.cornerCurve = kCACornerCurveCircular;
437
+ [self configureUnequalCornerMaskForGeometry:geometry];
438
+ layer.mask = _unequalCornerMask;
439
+ }
440
+ }
441
+
442
+ - (SmoothNormalizedClipGeometry)normalizedGeometryValue {
443
+ return {
444
+ .rect = _normalizedClip,
445
+ .radius = _normalizedRadius,
446
+ .radii = _normalizedRadii,
447
+ .curve = _normalizedCurve,
448
+ };
449
+ }
450
+
451
+ - (void)writeLayerGeometry:(SmoothNormalizedClipGeometry)geometry
452
+ hasExplicitRadii:(BOOL)hasExplicitRadii {
453
+ const BOOL rectChanged =
454
+ !CGRectEqualToRect(_normalizedClip, geometry.rect);
455
+ const BOOL radiiChanged =
456
+ !SmoothClipCornerRadiiEqual(_normalizedRadii, geometry.radii);
457
+ const BOOL curveChanged = _normalizedCurve != geometry.curve;
458
+ const BOOL representationChanged =
459
+ _normalizedHasExplicitRadii != hasExplicitRadii;
460
+ _normalizedClip = geometry.rect;
461
+ _normalizedRadius = geometry.radius;
462
+ _normalizedRadii = geometry.radii;
463
+ _normalizedCurve = geometry.curve;
464
+ _normalizedHasExplicitRadii = hasExplicitRadii;
465
+ if (!rectChanged && !radiiChanged && !curveChanged &&
466
+ !representationChanged) {
467
+ return;
468
+ }
254
469
 
255
470
  #if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
256
471
  const bool signpostsEnabled =
@@ -264,12 +479,13 @@ static std::array<double, 7> SmoothClipVelocityChannels(
264
479
  #endif
265
480
  CALayer *layer = _clipContainer.layer;
266
481
  if (rectChanged) {
267
- layer.bounds = rect;
268
- layer.position = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
269
- }
270
- if (radiusChanged) {
271
- layer.cornerRadius = radius;
482
+ layer.bounds = geometry.rect;
483
+ layer.position = CGPointMake(
484
+ CGRectGetMidX(geometry.rect), CGRectGetMidY(geometry.rect));
272
485
  }
486
+ // The uniform common case stays on CALayer's compositor fast path and uses
487
+ // the platform's exact continuous-corner implementation.
488
+ [self applyStaticCornerRepresentation:geometry];
273
489
  #if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
274
490
  if (signpostsEnabled) {
275
491
  os_signpost_interval_end(
@@ -278,11 +494,16 @@ static std::array<double, 7> SmoothClipVelocityChannels(
278
494
  #endif
279
495
  }
280
496
 
281
- - (void)writeContentTranslation:(CGPoint)translation {
282
- if (CGPointEqualToPoint(_normalizedContentTranslation, translation)) return;
497
+ - (void)writeContentTranslation:(CGPoint)translation
498
+ scale:(CGFloat)scale {
499
+ if (CGPointEqualToPoint(_normalizedContentTranslation, translation) &&
500
+ _normalizedContentScale == scale) {
501
+ return;
502
+ }
283
503
  _normalizedContentTranslation = translation;
504
+ _normalizedContentScale = scale;
284
505
  _contentContainer.layer.affineTransform =
285
- CGAffineTransformMakeTranslation(translation.x, translation.y);
506
+ SmoothClipContentTransform(scale, translation);
286
507
  }
287
508
 
288
509
  - (void)applyRequestedClip {
@@ -291,37 +512,47 @@ static std::array<double, 7> SmoothClipVelocityChannels(
291
512
 
292
513
  const BOOL visibilityChanged = _clipHidden != CGRectIsEmpty(geometry.rect);
293
514
  if (CGRectEqualToRect(_normalizedClip, geometry.rect) &&
294
- _normalizedRadius == geometry.radius &&
515
+ SmoothClipCornerRadiiEqual(_normalizedRadii, geometry.radii) &&
516
+ _normalizedCurve == geometry.curve &&
517
+ _normalizedHasExplicitRadii == _requestedHasExplicitRadii &&
295
518
  CGPointEqualToPoint(
296
519
  _normalizedContentTranslation, _requestedContentTranslation) &&
520
+ _normalizedContentScale == _requestedContentScale &&
297
521
  !visibilityChanged) {
298
522
  return;
299
523
  }
300
- [self writeLayerRect:geometry.rect radius:geometry.radius];
301
- [self writeContentTranslation:_requestedContentTranslation];
524
+ [self writeLayerGeometry:geometry
525
+ hasExplicitRadii:_requestedHasExplicitRadii];
526
+ [self writeContentTranslation:_requestedContentTranslation
527
+ scale:_requestedContentScale];
302
528
  [self syncVisibilityForRect:geometry.rect];
303
529
  }
304
530
 
305
531
  - (void)recordInteractiveRect:(CGRect)rect
306
- radius:(CGFloat)radius
307
- contentTranslation:(CGPoint)contentTranslation {
532
+ radii:(SmoothClipCornerRadii)radii
533
+ contentTranslation:(CGPoint)contentTranslation
534
+ contentScale:(CGFloat)contentScale {
308
535
  // Identical-value dedupe and same-frame coalescing live in the shared
309
536
  // tracker; coalescing keeps a distinct re-record issued sub-frame after
310
537
  // the last one (e.g. a fused animateTo `from` seed at gesture release)
311
538
  // from exploding the inherited velocity.
312
539
  smoothclip::recordVelocitySample(
313
540
  _velocitySamples,
314
- SmoothClipVelocityChannels(rect, radius, contentTranslation),
541
+ SmoothClipVelocityChannels(
542
+ rect, radii, contentTranslation, contentScale),
315
543
  CACurrentMediaTime());
316
544
  }
317
545
 
318
546
  - (CGFloat)inheritedVelocityToRect:(CGRect)target
319
- radius:(CGFloat)targetRadius
547
+ radii:(SmoothClipCornerRadii)targetRadii
320
548
  contentTranslation:(CGPoint)targetContentTranslation {
321
549
  return smoothclip::inheritedVelocity(
322
550
  _velocitySamples,
323
551
  SmoothClipVelocityChannels(
324
- target, targetRadius, targetContentTranslation),
552
+ target,
553
+ targetRadii,
554
+ targetContentTranslation,
555
+ _requestedContentScale),
325
556
  CACurrentMediaTime());
326
557
  }
327
558
 
@@ -338,29 +569,40 @@ static std::array<double, 7> SmoothClipVelocityChannels(
338
569
  // cancel-to-target): they must not enter the 'inherit' history.
339
570
  if (recordVelocitySample && _hasLayout) {
340
571
  [self recordInteractiveRect:_normalizedClip
341
- radius:_normalizedRadius
342
- contentTranslation:_normalizedContentTranslation];
572
+ radii:_normalizedRadii
573
+ contentTranslation:_normalizedContentTranslation
574
+ contentScale:_normalizedContentScale];
343
575
  }
344
576
  }
345
577
 
346
578
  - (smoothclip::Presentation)smoothClipCurrentPresentation {
347
- CGFloat visibleRadius = _normalizedRadius;
579
+ SmoothClipCornerRadii visibleRadii = _normalizedRadii;
348
580
  const CGRect visibleRect = _activeAnimationId != 0
349
- ? [self presentationRectWithRadius:&visibleRadius]
581
+ ? [self presentationRectWithRadii:&visibleRadii]
350
582
  : _normalizedClip;
351
583
  CALayer *contentLayer = _activeAnimationId != 0
352
584
  ? (CALayer *)_contentContainer.layer.presentationLayer
353
585
  : _contentContainer.layer;
354
586
  if (contentLayer == nil) contentLayer = _contentContainer.layer;
355
587
  const CGAffineTransform transform = contentLayer.affineTransform;
356
- return {
357
- {CGRectGetMinX(visibleRect),
358
- CGRectGetMinY(visibleRect),
359
- CGRectGetWidth(visibleRect),
360
- CGRectGetHeight(visibleRect),
361
- visibleRadius},
362
- transform.tx,
363
- transform.ty};
588
+ const BOOL uniform = SmoothClipCornerRadiiAreUniform(visibleRadii);
589
+ smoothclip::Geometry geometry{
590
+ CGRectGetMinX(visibleRect),
591
+ CGRectGetMinY(visibleRect),
592
+ CGRectGetWidth(visibleRect),
593
+ CGRectGetHeight(visibleRect),
594
+ uniform ? visibleRadii.topLeft : 0};
595
+ if (_normalizedHasExplicitRadii || !uniform) {
596
+ geometry.topLeftRadius = visibleRadii.topLeft;
597
+ geometry.topRightRadius = visibleRadii.topRight;
598
+ geometry.bottomRightRadius = visibleRadii.bottomRight;
599
+ geometry.bottomLeftRadius = visibleRadii.bottomLeft;
600
+ }
601
+ geometry.curve = _normalizedCurve == SmoothClipCornerCurveContinuous
602
+ ? smoothclip::ClipCurve::Continuous
603
+ : smoothclip::ClipCurve::Circular;
604
+ const CGFloat contentScale = hypot(transform.a, transform.b);
605
+ return {geometry, transform.tx, transform.ty, contentScale};
364
606
  }
365
607
 
366
608
  - (BOOL)smoothClipIsJoinable {
@@ -398,35 +640,39 @@ static std::array<double, 7> SmoothClipVelocityChannels(
398
640
  - (smoothclip::Presentation)smoothClipFreezePresentation {
399
641
  const smoothclip::Presentation visible =
400
642
  [self smoothClipCurrentPresentation];
401
- const CGRect visibleRect = CGRectMake(
402
- visible.clip.x,
403
- visible.clip.y,
404
- visible.clip.width,
405
- visible.clip.height);
406
- const CGPoint visibleContentTranslation = CGPointMake(
407
- visible.contentTranslateX,
408
- visible.contentTranslateY);
409
643
  [self stopLayerAnimationWithoutCallback];
410
- _requestedClip = visibleRect;
411
- _requestedRadius = visible.clip.radius;
412
- _requestedContentTranslation = visibleContentTranslation;
413
- [self writeLayerRect:visibleRect radius:visible.clip.radius];
414
- [self writeContentTranslation:visibleContentTranslation];
415
- [self syncVisibilityForRect:visibleRect];
644
+ [self storeRequestedPresentation:visible];
645
+ [self applyRequestedClip];
416
646
  return visible;
417
647
  }
418
648
 
419
- - (CGRect)presentationRectWithRadius:(CGFloat *)radius {
649
+ - (CGRect)presentationRectWithRadii:(SmoothClipCornerRadii *)radii {
420
650
  CALayer *layer = (CALayer *)_clipContainer.layer.presentationLayer;
421
651
  if (layer == nil) layer = _clipContainer.layer;
422
652
  const CGRect bounds = layer.bounds;
423
653
  const CGPoint position = layer.position;
424
- if (radius != nullptr) *radius = layer.cornerRadius;
425
- return CGRectMake(
654
+ const CGRect rect = CGRectMake(
426
655
  position.x - bounds.size.width * layer.anchorPoint.x,
427
656
  position.y - bounds.size.height * layer.anchorPoint.y,
428
657
  bounds.size.width,
429
658
  bounds.size.height);
659
+ if (radii != nullptr) {
660
+ if (_clipContainer.layer.mask == _unequalCornerMask) {
661
+ CAShapeLayer *mask =
662
+ (CAShapeLayer *)_unequalCornerMask.presentationLayer;
663
+ if (mask == nil) mask = _unequalCornerMask;
664
+ *radii = SmoothClipRadiiFromFixedPath(
665
+ mask.path, rect, _normalizedRadii);
666
+ } else {
667
+ *radii = {
668
+ layer.cornerRadius,
669
+ layer.cornerRadius,
670
+ layer.cornerRadius,
671
+ layer.cornerRadius,
672
+ };
673
+ }
674
+ }
675
+ return rect;
430
676
  }
431
677
 
432
678
  - (void)stopLayerAnimationWithoutCallback {
@@ -440,6 +686,8 @@ static std::array<double, 7> SmoothClipVelocityChannels(
440
686
  _ignoreAnimationCallback = YES;
441
687
  [_clipContainer.layer removeAnimationForKey:@"smoothClip.geometry"];
442
688
  [_contentContainer.layer removeAnimationForKey:@"smoothClip.content"];
689
+ [_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
690
+ [self applyStaticCornerRepresentation:[self normalizedGeometryValue]];
443
691
  _ignoreAnimationCallback = NO;
444
692
  _activeAnimationId = 0;
445
693
  _activeAnimationKind = 0;
@@ -486,13 +734,17 @@ static std::array<double, 7> SmoothClipVelocityChannels(
486
734
  return animation;
487
735
  }
488
736
 
489
- - (void)installAnimationFromRect:(CGRect)fromRect
490
- fromRadius:(CGFloat)fromRadius
491
- fromContentTranslation:(CGPoint)fromContentTranslation
492
- toRect:(CGRect)toRect
493
- toRadius:(CGFloat)toRadius
494
- toContentTranslation:(CGPoint)toContentTranslation
495
- duration:(CFTimeInterval)duration {
737
+ - (void)installAnimationFromGeometry:
738
+ (SmoothNormalizedClipGeometry)fromGeometry
739
+ fromContentTranslation:(CGPoint)fromContentTranslation
740
+ fromContentScale:(CGFloat)fromContentScale
741
+ toGeometry:
742
+ (SmoothNormalizedClipGeometry)toGeometry
743
+ hasExplicitToRadii:(BOOL)hasExplicitToRadii
744
+ toContentTranslation:(CGPoint)toContentTranslation
745
+ toContentScale:(CGFloat)toContentScale
746
+ duration:(CFTimeInterval)duration
747
+ sharedBeginTime:(CFTimeInterval)sharedBeginTime {
496
748
  const int32_t animationId = _activeAnimationId;
497
749
  const NSInteger animationKind = _activeAnimationKind;
498
750
  [self stopLayerAnimationWithoutCallback];
@@ -500,50 +752,80 @@ static std::array<double, 7> SmoothClipVelocityChannels(
500
752
  _activeAnimationKind = animationKind;
501
753
  // Unhide while any animated frame can show content; an empty-to-empty
502
754
  // transition must stay hidden for accessibility.
503
- if (!CGRectIsEmpty(toRect) || !CGRectIsEmpty(fromRect)) {
755
+ if (!CGRectIsEmpty(toGeometry.rect) ||
756
+ !CGRectIsEmpty(fromGeometry.rect)) {
504
757
  [self setClipContainerHidden:NO];
505
758
  }
506
759
 
507
760
  CALayer *layer = _clipContainer.layer;
508
- const CGRect toBounds = toRect;
761
+ const CGRect toBounds = toGeometry.rect;
509
762
  const CGPoint fromPosition =
510
- CGPointMake(CGRectGetMidX(fromRect), CGRectGetMidY(fromRect));
763
+ CGPointMake(
764
+ CGRectGetMidX(fromGeometry.rect),
765
+ CGRectGetMidY(fromGeometry.rect));
511
766
  const CGPoint toPosition =
512
- CGPointMake(CGRectGetMidX(toRect), CGRectGetMidY(toRect));
513
- layer.bounds = toBounds;
514
- layer.position = toPosition;
515
- layer.cornerRadius = toRadius;
516
- _contentContainer.layer.affineTransform = CGAffineTransformMakeTranslation(
517
- toContentTranslation.x,
518
- toContentTranslation.y);
519
- _normalizedClip = toRect;
520
- _normalizedRadius = toRadius;
521
- _normalizedContentTranslation = toContentTranslation;
767
+ CGPointMake(
768
+ CGRectGetMidX(toGeometry.rect),
769
+ CGRectGetMidY(toGeometry.rect));
770
+ const BOOL usesMask =
771
+ !SmoothClipCornerRadiiAreUniform(fromGeometry.radii) ||
772
+ !SmoothClipCornerRadiiAreUniform(toGeometry.radii) ||
773
+ fromGeometry.curve != toGeometry.curve;
774
+ [self writeLayerGeometry:toGeometry
775
+ hasExplicitRadii:hasExplicitToRadii];
776
+ [self writeContentTranslation:toContentTranslation scale:toContentScale];
777
+ if (usesMask) {
778
+ // Keep one mask representation for the full interval, including an
779
+ // unequal→uniform transition. Switching layer.mask mid-animation would
780
+ // create a one-frame seam even though the underlying paths are compatible.
781
+ [self configureUnequalCornerMaskForGeometry:toGeometry];
782
+ layer.cornerRadius = 0;
783
+ layer.mask = _unequalCornerMask;
784
+ }
785
+
786
+ CGPathRef fromMaskPath = usesMask
787
+ ? SmoothClipCreateRoundedRectPath(
788
+ fromGeometry.rect, fromGeometry.radii, fromGeometry.curve)
789
+ : nil;
790
+ CGPathRef toMaskPath = usesMask
791
+ ? SmoothClipCreateRoundedRectPath(
792
+ toGeometry.rect, toGeometry.radii, toGeometry.curve)
793
+ : nil;
522
794
 
523
795
  CAAnimationGroup *group = [CAAnimationGroup animation];
524
796
  CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
797
+ CAAnimation *maskAnimation = nil;
525
798
  if (_activeAnimationKind == 1) {
526
799
  CAMediaTimingFunction *timing = [CAMediaTimingFunction
527
800
  functionWithControlPoints:_timingAnimation.controlPoint1X
528
801
  :_timingAnimation.controlPoint1Y
529
802
  :_timingAnimation.controlPoint2X
530
803
  :_timingAnimation.controlPoint2Y];
531
- group.animations = @[
804
+ NSMutableArray<CAAnimation *> *geometryAnimations = [NSMutableArray arrayWithArray:@[
532
805
  [self basicAnimationForKeyPath:@"bounds"
533
- fromValue:[NSValue valueWithCGRect:fromRect]
806
+ fromValue:[NSValue valueWithCGRect:fromGeometry.rect]
534
807
  toValue:[NSValue valueWithCGRect:toBounds]
535
808
  timingFunction:timing],
536
809
  [self basicAnimationForKeyPath:@"position"
537
810
  fromValue:[NSValue valueWithCGPoint:fromPosition]
538
811
  toValue:[NSValue valueWithCGPoint:toPosition]
539
812
  timingFunction:timing],
540
- [self basicAnimationForKeyPath:@"cornerRadius"
541
- fromValue:@(fromRadius)
542
- toValue:@(toRadius)
543
- timingFunction:timing],
544
- ];
813
+ ]];
814
+ if (usesMask) {
815
+ maskAnimation = [self basicAnimationForKeyPath:@"path"
816
+ fromValue:(__bridge id)fromMaskPath
817
+ toValue:(__bridge id)toMaskPath
818
+ timingFunction:timing];
819
+ } else {
820
+ [geometryAnimations addObject:
821
+ [self basicAnimationForKeyPath:@"cornerRadius"
822
+ fromValue:@(fromGeometry.radii.topLeft)
823
+ toValue:@(toGeometry.radii.topLeft)
824
+ timingFunction:timing]];
825
+ }
826
+ group.animations = geometryAnimations;
545
827
  group.duration = duration;
546
- contentGroup.animations = @[
828
+ NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
547
829
  [self basicAnimationForKeyPath:@"transform.translation.x"
548
830
  fromValue:@(fromContentTranslation.x)
549
831
  toValue:@(toContentTranslation.x)
@@ -552,7 +834,15 @@ static std::array<double, 7> SmoothClipVelocityChannels(
552
834
  fromValue:@(fromContentTranslation.y)
553
835
  toValue:@(toContentTranslation.y)
554
836
  timingFunction:timing],
555
- ];
837
+ ]];
838
+ if (fromContentScale != 1 || toContentScale != 1) {
839
+ [contentAnimations addObject:
840
+ [self basicAnimationForKeyPath:@"transform.scale"
841
+ fromValue:@(fromContentScale)
842
+ toValue:@(toContentScale)
843
+ timingFunction:timing]];
844
+ }
845
+ contentGroup.animations = contentAnimations;
556
846
  contentGroup.duration = duration;
557
847
  } else {
558
848
  // `_springAnimation.initialVelocity` is the interactive motion projected
@@ -565,24 +855,24 @@ static std::array<double, 7> SmoothClipVelocityChannels(
565
855
  const double velocity = _springAnimation.initialVelocity;
566
856
  const CFTimeInterval springDuration =
567
857
  [self springSettlingDurationWithVelocity:velocity];
568
- group.animations = @[
858
+ NSMutableArray<CAAnimation *> *geometryAnimations = [NSMutableArray arrayWithArray:@[
569
859
  [self springAnimationForKeyPath:@"bounds.origin.x"
570
- fromValue:@(fromRect.origin.x)
860
+ fromValue:@(fromGeometry.rect.origin.x)
571
861
  toValue:@(toBounds.origin.x)
572
862
  velocity:velocity
573
863
  duration:springDuration],
574
864
  [self springAnimationForKeyPath:@"bounds.origin.y"
575
- fromValue:@(fromRect.origin.y)
865
+ fromValue:@(fromGeometry.rect.origin.y)
576
866
  toValue:@(toBounds.origin.y)
577
867
  velocity:velocity
578
868
  duration:springDuration],
579
869
  [self springAnimationForKeyPath:@"bounds.size.width"
580
- fromValue:@(fromRect.size.width)
870
+ fromValue:@(fromGeometry.rect.size.width)
581
871
  toValue:@(toBounds.size.width)
582
872
  velocity:velocity
583
873
  duration:springDuration],
584
874
  [self springAnimationForKeyPath:@"bounds.size.height"
585
- fromValue:@(fromRect.size.height)
875
+ fromValue:@(fromGeometry.rect.size.height)
586
876
  toValue:@(toBounds.size.height)
587
877
  velocity:velocity
588
878
  duration:springDuration],
@@ -596,17 +886,27 @@ static std::array<double, 7> SmoothClipVelocityChannels(
596
886
  toValue:@(toPosition.y)
597
887
  velocity:velocity
598
888
  duration:springDuration],
599
- [self springAnimationForKeyPath:@"cornerRadius"
600
- fromValue:@(fromRadius)
601
- toValue:@(toRadius)
602
- velocity:velocity
603
- duration:springDuration],
604
- ];
889
+ ]];
890
+ if (usesMask) {
891
+ maskAnimation = [self springAnimationForKeyPath:@"path"
892
+ fromValue:(__bridge id)fromMaskPath
893
+ toValue:(__bridge id)toMaskPath
894
+ velocity:velocity
895
+ duration:springDuration];
896
+ } else {
897
+ [geometryAnimations addObject:
898
+ [self springAnimationForKeyPath:@"cornerRadius"
899
+ fromValue:@(fromGeometry.radii.topLeft)
900
+ toValue:@(toGeometry.radii.topLeft)
901
+ velocity:velocity
902
+ duration:springDuration]];
903
+ }
904
+ group.animations = geometryAnimations;
605
905
  // A rebuilt spring (layout change, foreground resume) must run its own
606
906
  // settling time; clamping to the interrupted run's remaining wall-clock
607
907
  // time would truncate the oscillation and snap to the target.
608
908
  group.duration = springDuration;
609
- contentGroup.animations = @[
909
+ NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
610
910
  [self springAnimationForKeyPath:@"transform.translation.x"
611
911
  fromValue:@(fromContentTranslation.x)
612
912
  toValue:@(toContentTranslation.x)
@@ -617,12 +917,42 @@ static std::array<double, 7> SmoothClipVelocityChannels(
617
917
  toValue:@(toContentTranslation.y)
618
918
  velocity:velocity
619
919
  duration:springDuration],
620
- ];
920
+ ]];
921
+ if (fromContentScale != 1 || toContentScale != 1) {
922
+ [contentAnimations addObject:
923
+ [self springAnimationForKeyPath:@"transform.scale"
924
+ fromValue:@(fromContentScale)
925
+ toValue:@(toContentScale)
926
+ velocity:velocity
927
+ duration:springDuration]];
928
+ }
929
+ contentGroup.animations = contentAnimations;
621
930
  contentGroup.duration = springDuration;
622
931
  }
623
932
 
933
+ if (maskAnimation != nil) {
934
+ maskAnimation.duration = group.duration;
935
+ }
936
+
937
+ if (sharedBeginTime > 0) {
938
+ // Each layer receives the same absolute media timestamp translated into
939
+ // its local clock. Group participants therefore advance from one epoch
940
+ // even though their animations are installed sequentially.
941
+ group.beginTime = [layer convertTime:sharedBeginTime fromLayer:nil];
942
+ contentGroup.beginTime = [_contentContainer.layer
943
+ convertTime:sharedBeginTime
944
+ fromLayer:nil];
945
+ if (maskAnimation != nil) {
946
+ maskAnimation.beginTime = [_unequalCornerMask
947
+ convertTime:sharedBeginTime
948
+ fromLayer:nil];
949
+ }
950
+ }
951
+
624
952
  _animationDuration = group.duration;
625
- _animationStartedAt = CACurrentMediaTime();
953
+ _animationStartedAt = sharedBeginTime > 0
954
+ ? sharedBeginTime
955
+ : CACurrentMediaTime();
626
956
  _animationDelegate = [SmoothClipAnimationDelegate new];
627
957
  _animationDelegate.view = self;
628
958
  _animationDelegate.driverId = _driverId;
@@ -631,10 +961,17 @@ static std::array<double, 7> SmoothClipVelocityChannels(
631
961
  [layer addAnimation:group forKey:@"smoothClip.geometry"];
632
962
  [_contentContainer.layer addAnimation:contentGroup
633
963
  forKey:@"smoothClip.content"];
964
+ if (maskAnimation != nil) {
965
+ [_unequalCornerMask addAnimation:maskAnimation forKey:@"smoothClip.mask"];
966
+ }
967
+ if (fromMaskPath != nil) CGPathRelease(fromMaskPath);
968
+ if (toMaskPath != nil) CGPathRelease(toMaskPath);
634
969
  _animationInstallGeneration += 1;
635
970
  }
636
971
 
637
- - (BOOL)startAnimationToRequestedGeometryWithDuration:(CFTimeInterval)duration {
972
+ - (BOOL)startAnimationToRequestedGeometryWithDuration:(CFTimeInterval)duration
973
+ sharedBeginTime:
974
+ (CFTimeInterval)sharedBeginTime {
638
975
  if (![self smoothClipCanDisplay]) {
639
976
  _pendingAnimationInstall = YES;
640
977
  return NO;
@@ -642,36 +979,69 @@ static std::array<double, 7> SmoothClipVelocityChannels(
642
979
  SmoothNormalizedClipGeometry target;
643
980
  if (![self normalizedRequestedGeometry:&target]) return NO;
644
981
  const smoothclip::Presentation current = [self smoothClipCurrentPresentation];
645
- [self installAnimationFromRect:CGRectMake(
646
- current.clip.x,
647
- current.clip.y,
648
- current.clip.width,
649
- current.clip.height)
650
- fromRadius:current.clip.radius
651
- fromContentTranslation:CGPointMake(
652
- current.contentTranslateX,
653
- current.contentTranslateY)
654
- toRect:target.rect
655
- toRadius:target.radius
656
- toContentTranslation:_requestedContentTranslation
657
- duration:duration];
982
+ const SmoothClipCornerRadii currentRadii =
983
+ SmoothClipPresentationRadii(current.clip);
984
+ const SmoothNormalizedClipGeometry fromGeometry = {
985
+ .rect = CGRectMake(
986
+ current.clip.x,
987
+ current.clip.y,
988
+ current.clip.width,
989
+ current.clip.height),
990
+ .radius = SmoothClipCornerRadiiAreUniform(currentRadii)
991
+ ? currentRadii.topLeft
992
+ : 0,
993
+ .radii = currentRadii,
994
+ .curve = SmoothClipPresentationCurve(current.clip),
995
+ };
996
+ [self installAnimationFromGeometry:fromGeometry
997
+ fromContentTranslation:CGPointMake(
998
+ current.contentTranslateX,
999
+ current.contentTranslateY)
1000
+ fromContentScale:current.contentScale
1001
+ toGeometry:target
1002
+ hasExplicitToRadii:_requestedHasExplicitRadii
1003
+ toContentTranslation:_requestedContentTranslation
1004
+ toContentScale:_requestedContentScale
1005
+ duration:duration
1006
+ sharedBeginTime:sharedBeginTime];
658
1007
  return YES;
659
1008
  }
660
1009
 
661
1010
  - (BOOL)smoothClipAnimateTiming:(smoothclip::Presentation)presentation
662
1011
  animation:(smoothclip::TimingAnimation)animation
663
1012
  animationId:(int32_t)animationId {
1013
+ return [self smoothClipAnimateTiming:presentation
1014
+ animation:animation
1015
+ animationId:animationId
1016
+ sharedBeginTime:0];
1017
+ }
1018
+
1019
+ - (BOOL)smoothClipAnimateTiming:(smoothclip::Presentation)presentation
1020
+ animation:(smoothclip::TimingAnimation)animation
1021
+ animationId:(int32_t)animationId
1022
+ sharedBeginTime:(CFTimeInterval)sharedBeginTime {
664
1023
  [self storeRequestedPresentation:presentation];
665
1024
  _timingAnimation = animation;
666
1025
  _activeAnimationKind = 1;
667
1026
  _activeAnimationId = animationId;
668
1027
  return [self startAnimationToRequestedGeometryWithDuration:
669
- MAX(0, animation.durationMs) / 1000.0];
1028
+ MAX(0, animation.durationMs) / 1000.0
1029
+ sharedBeginTime:sharedBeginTime];
670
1030
  }
671
1031
 
672
1032
  - (BOOL)smoothClipAnimateSpring:(smoothclip::Presentation)presentation
673
1033
  animation:(smoothclip::SpringAnimation)animation
674
1034
  animationId:(int32_t)animationId {
1035
+ return [self smoothClipAnimateSpring:presentation
1036
+ animation:animation
1037
+ animationId:animationId
1038
+ sharedBeginTime:0];
1039
+ }
1040
+
1041
+ - (BOOL)smoothClipAnimateSpring:(smoothclip::Presentation)presentation
1042
+ animation:(smoothclip::SpringAnimation)animation
1043
+ animationId:(int32_t)animationId
1044
+ sharedBeginTime:(CFTimeInterval)sharedBeginTime {
675
1045
  [self storeRequestedPresentation:presentation];
676
1046
  _springAnimation = animation;
677
1047
  if (animation.inheritVelocity) {
@@ -679,7 +1049,7 @@ static std::array<double, 7> SmoothClipVelocityChannels(
679
1049
  if ([self normalizedRequestedGeometry:&target]) {
680
1050
  _springAnimation.initialVelocity =
681
1051
  [self inheritedVelocityToRect:target.rect
682
- radius:target.radius
1052
+ radii:target.radii
683
1053
  contentTranslation:_requestedContentTranslation];
684
1054
  } else {
685
1055
  _springAnimation.initialVelocity = 0;
@@ -687,7 +1057,8 @@ static std::array<double, 7> SmoothClipVelocityChannels(
687
1057
  }
688
1058
  _activeAnimationKind = 2;
689
1059
  _activeAnimationId = animationId;
690
- return [self startAnimationToRequestedGeometryWithDuration:0];
1060
+ return [self startAnimationToRequestedGeometryWithDuration:0
1061
+ sharedBeginTime:sharedBeginTime];
691
1062
  }
692
1063
 
693
1064
  - (CAKeyframeAnimation *)keyframeAnimationForKeyPath:(NSString *)keyPath
@@ -705,6 +1076,18 @@ static std::array<double, 7> SmoothClipVelocityChannels(
705
1076
  keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
706
1077
  durationMs:(double)durationMs
707
1078
  animationId:(int32_t)animationId {
1079
+ return [self smoothClipAnimateKeyframes:presentation
1080
+ keyframes:keyframes
1081
+ durationMs:durationMs
1082
+ animationId:animationId
1083
+ sharedBeginTime:0];
1084
+ }
1085
+
1086
+ - (BOOL)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
1087
+ keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
1088
+ durationMs:(double)durationMs
1089
+ animationId:(int32_t)animationId
1090
+ sharedBeginTime:(CFTimeInterval)sharedBeginTime {
708
1091
  if (keyframes.size() < 2) return NO;
709
1092
  [self storeRequestedPresentation:presentation];
710
1093
  _activeAnimationKind = 3;
@@ -725,20 +1108,47 @@ static std::array<double, 7> SmoothClipVelocityChannels(
725
1108
  NSMutableArray *positionValues =
726
1109
  [NSMutableArray arrayWithCapacity:frameCount];
727
1110
  NSMutableArray *radiusValues = [NSMutableArray arrayWithCapacity:frameCount];
1111
+ NSMutableArray *maskPathValues =
1112
+ [NSMutableArray arrayWithCapacity:frameCount];
728
1113
  NSMutableArray *translateXValues =
729
1114
  [NSMutableArray arrayWithCapacity:frameCount];
730
1115
  NSMutableArray *translateYValues =
731
1116
  [NSMutableArray arrayWithCapacity:frameCount];
1117
+ NSMutableArray *scaleValues =
1118
+ [NSMutableArray arrayWithCapacity:frameCount];
1119
+ BOOL usesMask = NO;
1120
+ BOOL animatesScale = NO;
1121
+ SmoothClipCornerCurve firstCurve = SmoothClipCornerCurveCircular;
1122
+ BOOL hasFirstCurve = NO;
1123
+ std::vector<SmoothNormalizedClipGeometry> normalizedFrames;
1124
+ normalizedFrames.reserve(frameCount);
732
1125
  for (const smoothclip::Keyframe &frame : keyframes) {
1126
+ const SmoothClipCornerRadii frameRadii =
1127
+ SmoothClipPresentationRadii(frame.presentation.clip);
1128
+ const SmoothClipCornerCurve frameCurve =
1129
+ SmoothClipPresentationCurve(frame.presentation.clip);
733
1130
  SmoothNormalizedClipGeometry normalized;
734
- if (!SmoothClipNormalizeGeometry(
1131
+ if (!SmoothClipNormalizeGeometryV2(
735
1132
  frame.presentation.clip.x,
736
1133
  frame.presentation.clip.y,
737
1134
  frame.presentation.clip.width,
738
1135
  frame.presentation.clip.height,
739
- frame.presentation.clip.radius,
1136
+ frameRadii.topLeft,
1137
+ frameRadii.topRight,
1138
+ frameRadii.bottomRight,
1139
+ frameRadii.bottomLeft,
1140
+ frameCurve,
740
1141
  hostSize,
741
1142
  &normalized)) return NO;
1143
+ normalizedFrames.push_back(normalized);
1144
+ if (!SmoothClipCornerRadiiAreUniform(normalized.radii)) usesMask = YES;
1145
+ if (!hasFirstCurve) {
1146
+ firstCurve = normalized.curve;
1147
+ hasFirstCurve = YES;
1148
+ } else if (normalized.curve != firstCurve) {
1149
+ usesMask = YES;
1150
+ }
1151
+ if (frame.presentation.contentScale != 1) animatesScale = YES;
742
1152
  [times addObject:@(frame.offset)];
743
1153
  [boundsValues addObject:[NSValue valueWithCGRect:normalized.rect]];
744
1154
  [positionValues addObject:[NSValue valueWithCGPoint:CGPointMake(
@@ -746,40 +1156,91 @@ static std::array<double, 7> SmoothClipVelocityChannels(
746
1156
  [radiusValues addObject:@(normalized.radius)];
747
1157
  [translateXValues addObject:@(frame.presentation.contentTranslateX)];
748
1158
  [translateYValues addObject:@(frame.presentation.contentTranslateY)];
1159
+ [scaleValues addObject:@(frame.presentation.contentScale)];
1160
+ }
1161
+
1162
+ if (usesMask) {
1163
+ for (const SmoothNormalizedClipGeometry &normalized : normalizedFrames) {
1164
+ CGPathRef path = SmoothClipCreateRoundedRectPath(
1165
+ normalized.rect, normalized.radii, normalized.curve);
1166
+ [maskPathValues addObject:(__bridge id)path];
1167
+ CGPathRelease(path);
1168
+ }
749
1169
  }
750
1170
 
751
1171
  SmoothNormalizedClipGeometry target;
752
1172
  if (![self normalizedRequestedGeometry:&target]) return NO;
753
- [self writeLayerRect:target.rect radius:target.radius];
754
- [self writeContentTranslation:_requestedContentTranslation];
1173
+ [self writeLayerGeometry:target
1174
+ hasExplicitRadii:_requestedHasExplicitRadii];
1175
+ [self writeContentTranslation:_requestedContentTranslation
1176
+ scale:_requestedContentScale];
1177
+ if (usesMask) {
1178
+ [self configureUnequalCornerMaskForGeometry:target];
1179
+ _clipContainer.layer.cornerRadius = 0;
1180
+ _clipContainer.layer.mask = _unequalCornerMask;
1181
+ }
755
1182
  [self setClipContainerHidden:NO];
756
1183
 
757
1184
  CAAnimationGroup *group = [CAAnimationGroup animation];
758
- group.animations = @[
1185
+ NSMutableArray<CAAnimation *> *geometryAnimations = [NSMutableArray arrayWithArray:@[
759
1186
  [self keyframeAnimationForKeyPath:@"bounds"
760
1187
  values:boundsValues
761
1188
  keyTimes:times],
762
1189
  [self keyframeAnimationForKeyPath:@"position"
763
1190
  values:positionValues
764
1191
  keyTimes:times],
765
- [self keyframeAnimationForKeyPath:@"cornerRadius"
766
- values:radiusValues
767
- keyTimes:times],
768
- ];
1192
+ ]];
1193
+ if (!usesMask) {
1194
+ [geometryAnimations addObject:
1195
+ [self keyframeAnimationForKeyPath:@"cornerRadius"
1196
+ values:radiusValues
1197
+ keyTimes:times]];
1198
+ }
1199
+ group.animations = geometryAnimations;
769
1200
  group.duration = MAX(0, durationMs) / 1000.0;
770
1201
  CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
771
- contentGroup.animations = @[
1202
+ NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
772
1203
  [self keyframeAnimationForKeyPath:@"transform.translation.x"
773
1204
  values:translateXValues
774
1205
  keyTimes:times],
775
1206
  [self keyframeAnimationForKeyPath:@"transform.translation.y"
776
1207
  values:translateYValues
777
1208
  keyTimes:times],
778
- ];
1209
+ ]];
1210
+ if (animatesScale) {
1211
+ [contentAnimations addObject:
1212
+ [self keyframeAnimationForKeyPath:@"transform.scale"
1213
+ values:scaleValues
1214
+ keyTimes:times]];
1215
+ }
1216
+ contentGroup.animations = contentAnimations;
779
1217
  contentGroup.duration = group.duration;
780
1218
 
1219
+ CAKeyframeAnimation *maskAnimation = usesMask
1220
+ ? [self keyframeAnimationForKeyPath:@"path"
1221
+ values:maskPathValues
1222
+ keyTimes:times]
1223
+ : nil;
1224
+ maskAnimation.duration = group.duration;
1225
+
1226
+ if (sharedBeginTime > 0) {
1227
+ group.beginTime = [_clipContainer.layer
1228
+ convertTime:sharedBeginTime
1229
+ fromLayer:nil];
1230
+ contentGroup.beginTime = [_contentContainer.layer
1231
+ convertTime:sharedBeginTime
1232
+ fromLayer:nil];
1233
+ if (maskAnimation != nil) {
1234
+ maskAnimation.beginTime = [_unequalCornerMask
1235
+ convertTime:sharedBeginTime
1236
+ fromLayer:nil];
1237
+ }
1238
+ }
1239
+
781
1240
  _animationDuration = group.duration;
782
- _animationStartedAt = CACurrentMediaTime();
1241
+ _animationStartedAt = sharedBeginTime > 0
1242
+ ? sharedBeginTime
1243
+ : CACurrentMediaTime();
783
1244
  _animationDelegate = [SmoothClipAnimationDelegate new];
784
1245
  _animationDelegate.view = self;
785
1246
  _animationDelegate.driverId = _driverId;
@@ -788,6 +1249,9 @@ static std::array<double, 7> SmoothClipVelocityChannels(
788
1249
  [_clipContainer.layer addAnimation:group forKey:@"smoothClip.geometry"];
789
1250
  [_contentContainer.layer addAnimation:contentGroup
790
1251
  forKey:@"smoothClip.content"];
1252
+ if (maskAnimation != nil) {
1253
+ [_unequalCornerMask addAnimation:maskAnimation forKey:@"smoothClip.mask"];
1254
+ }
791
1255
  _animationInstallGeneration += 1;
792
1256
  return YES;
793
1257
  }
@@ -796,24 +1260,12 @@ static std::array<double, 7> SmoothClipVelocityChannels(
796
1260
  if (_activeAnimationId == 0) return;
797
1261
  const smoothclip::Presentation visible =
798
1262
  [self smoothClipCurrentPresentation];
799
- const CGRect visibleRect = CGRectMake(
800
- visible.clip.x,
801
- visible.clip.y,
802
- visible.clip.width,
803
- visible.clip.height);
804
- const CGPoint visibleContentTranslation = CGPointMake(
805
- visible.contentTranslateX,
806
- visible.contentTranslateY);
807
1263
  [self stopLayerAnimationWithoutCallback];
808
1264
  if (useTarget) {
809
1265
  [self applyRequestedClip];
810
1266
  } else {
811
- _requestedClip = visibleRect;
812
- _requestedRadius = visible.clip.radius;
813
- _requestedContentTranslation = visibleContentTranslation;
814
- [self writeLayerRect:visibleRect radius:visible.clip.radius];
815
- [self writeContentTranslation:visibleContentTranslation];
816
- [self syncVisibilityForRect:visibleRect];
1267
+ [self storeRequestedPresentation:visible];
1268
+ [self applyRequestedClip];
817
1269
  }
818
1270
  }
819
1271
 
@@ -824,6 +1276,8 @@ static std::array<double, 7> SmoothClipVelocityChannels(
824
1276
  _activeAnimationId = 0;
825
1277
  _activeAnimationKind = 0;
826
1278
  _animationDelegate = nil;
1279
+ [_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
1280
+ [self applyStaticCornerRepresentation:[self normalizedGeometryValue]];
827
1281
  [self syncVisibilityForRect:_normalizedClip];
828
1282
  smoothclip::viewAnimationDidStop(
829
1283
  driverId, animationId, self, finished);
@@ -839,14 +1293,42 @@ static std::array<double, 7> SmoothClipVelocityChannels(
839
1293
  isfinite(newProps.driverId) && newProps.driverId > 0
840
1294
  ? static_cast<uint64_t>(newProps.driverId)
841
1295
  : 0;
842
- const smoothclip::Presentation initial{
843
- {newProps.initialClipX,
844
- newProps.initialClipY,
845
- newProps.initialClipWidth,
846
- newProps.initialClipHeight,
847
- newProps.initialClipRadius},
848
- newProps.initialContentTranslateX,
849
- newProps.initialContentTranslateY};
1296
+ smoothclip::Presentation initial{};
1297
+ if (newProps.presentationVersion == 2) {
1298
+ if (!SmoothClipBuildPresentationV2(
1299
+ newProps.initialClipX,
1300
+ newProps.initialClipY,
1301
+ newProps.initialClipWidth,
1302
+ newProps.initialClipHeight,
1303
+ newProps.initialClipTopLeftRadius,
1304
+ newProps.initialClipTopRightRadius,
1305
+ newProps.initialClipBottomRightRadius,
1306
+ newProps.initialClipBottomLeftRadius,
1307
+ newProps.initialClipCurve,
1308
+ newProps.initialContentTranslateX,
1309
+ newProps.initialContentTranslateY,
1310
+ newProps.initialContentScale,
1311
+ &initial)) {
1312
+ // The initial V2 presentation is one atomic value. Do not unregister an
1313
+ // old driver or apply a subset of valid channels when any field rejects.
1314
+ return;
1315
+ }
1316
+ } else if (newProps.presentationVersion == 0 ||
1317
+ newProps.presentationVersion == 1) {
1318
+ // Version zero is what old JS produces when the versioned prop does not
1319
+ // exist. Preserve that old-JS/new-native path byte-for-byte as V1.
1320
+ initial = {
1321
+ {newProps.initialClipX,
1322
+ newProps.initialClipY,
1323
+ newProps.initialClipWidth,
1324
+ newProps.initialClipHeight,
1325
+ newProps.initialClipRadius},
1326
+ newProps.initialContentTranslateX,
1327
+ newProps.initialContentTranslateY};
1328
+ } else {
1329
+ // A future protocol must not be silently interpreted as V1.
1330
+ return;
1331
+ }
850
1332
 
851
1333
  if (nextDriverId != _driverId) {
852
1334
  if (_driverId != 0) {
@@ -872,22 +1354,25 @@ static std::array<double, 7> SmoothClipVelocityChannels(
872
1354
 
873
1355
  - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
874
1356
  oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
875
- CGFloat visibleRadius = 0;
876
1357
  const BOOL wasAnimating = _activeAnimationId != 0;
877
1358
  const BOOL wasPending = _pendingAnimationInstall;
878
1359
  const uint32_t installGenerationAtEntry = _animationInstallGeneration;
879
1360
  const smoothclip::Presentation visible = wasAnimating && !wasPending
880
1361
  ? [self smoothClipCurrentPresentation]
881
- : smoothclip::Presentation{{0, 0, 0, 0, 0}, 0, 0};
1362
+ : smoothclip::Presentation{{0, 0, 0, 0, 0}, 0, 0, 1};
882
1363
  const CGRect visibleRect = CGRectMake(
883
1364
  visible.clip.x,
884
1365
  visible.clip.y,
885
1366
  visible.clip.width,
886
1367
  visible.clip.height);
887
- visibleRadius = visible.clip.radius;
1368
+ const SmoothClipCornerRadii visibleRadii =
1369
+ SmoothClipPresentationRadii(visible.clip);
1370
+ const SmoothClipCornerCurve visibleCurve =
1371
+ SmoothClipPresentationCurve(visible.clip);
888
1372
  const CGPoint visibleContentTranslation = CGPointMake(
889
1373
  visible.contentTranslateX,
890
1374
  visible.contentTranslateY);
1375
+ const CGFloat visibleContentScale = visible.contentScale;
891
1376
  CFTimeInterval remaining = wasAnimating
892
1377
  ? MAX(0, _animationDuration -
893
1378
  (CACurrentMediaTime() - _animationStartedAt))
@@ -943,23 +1428,29 @@ static std::array<double, 7> SmoothClipVelocityChannels(
943
1428
  _timingAnimation = {remaining * 1000.0, 0, 0, 1, 1, 2};
944
1429
  }
945
1430
  SmoothNormalizedClipGeometry current;
946
- SmoothClipNormalizeGeometry(
1431
+ SmoothClipNormalizeGeometryV2(
947
1432
  CGRectGetMinX(visibleRect),
948
1433
  CGRectGetMinY(visibleRect),
949
1434
  CGRectGetWidth(visibleRect),
950
1435
  CGRectGetHeight(visibleRect),
951
- visibleRadius,
1436
+ visibleRadii.topLeft,
1437
+ visibleRadii.topRight,
1438
+ visibleRadii.bottomRight,
1439
+ visibleRadii.bottomLeft,
1440
+ visibleCurve,
952
1441
  self.bounds.size,
953
1442
  &current);
954
1443
  SmoothNormalizedClipGeometry target;
955
1444
  if ([self normalizedRequestedGeometry:&target]) {
956
- [self installAnimationFromRect:current.rect
957
- fromRadius:current.radius
958
- fromContentTranslation:visibleContentTranslation
959
- toRect:target.rect
960
- toRadius:target.radius
961
- toContentTranslation:_requestedContentTranslation
962
- duration:remaining];
1445
+ [self installAnimationFromGeometry:current
1446
+ fromContentTranslation:visibleContentTranslation
1447
+ fromContentScale:visibleContentScale
1448
+ toGeometry:target
1449
+ hasExplicitToRadii:_requestedHasExplicitRadii
1450
+ toContentTranslation:_requestedContentTranslation
1451
+ toContentScale:_requestedContentScale
1452
+ duration:remaining
1453
+ sharedBeginTime:0];
963
1454
  }
964
1455
  } else {
965
1456
  if (_driverId != 0 && smoothclip::hasActiveAnimation(_driverId)) {
@@ -974,31 +1465,27 @@ static std::array<double, 7> SmoothClipVelocityChannels(
974
1465
 
975
1466
  - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
976
1467
  if (![super pointInside:point withEvent:event]) return NO;
977
- CGFloat radius = 0;
1468
+ SmoothClipCornerRadii radii = _normalizedRadii;
978
1469
  const CGRect clip = _activeAnimationId != 0
979
- ? [self presentationRectWithRadius:&radius]
1470
+ ? [self presentationRectWithRadii:&radii]
980
1471
  : _normalizedClip;
981
- if (_activeAnimationId == 0) radius = _normalizedRadius;
982
1472
  if (CGRectIsEmpty(clip) || !CGRectContainsPoint(clip, point)) return NO;
983
- if (radius <= 0) return YES;
984
-
985
- const CGFloat innerLeft = CGRectGetMinX(clip) + radius;
986
- const CGFloat innerRight = CGRectGetMaxX(clip) - radius;
987
- const CGFloat innerTop = CGRectGetMinY(clip) + radius;
988
- const CGFloat innerBottom = CGRectGetMaxY(clip) - radius;
989
- if ((point.x >= innerLeft && point.x <= innerRight) ||
990
- (point.y >= innerTop && point.y <= innerBottom)) {
991
- return YES;
1473
+ CGPathRef path = nil;
1474
+ if (_clipContainer.layer.mask == _unequalCornerMask) {
1475
+ CAShapeLayer *mask = _activeAnimationId != 0
1476
+ ? (CAShapeLayer *)_unequalCornerMask.presentationLayer
1477
+ : _unequalCornerMask;
1478
+ if (mask == nil) mask = _unequalCornerMask;
1479
+ path = mask.path;
1480
+ return path != nil && CGPathContainsPoint(path, NULL, point, NO);
992
1481
  }
993
-
994
- const CGFloat centerX = point.x < innerLeft ? innerLeft : innerRight;
995
- const CGFloat centerY = point.y < innerTop ? innerTop : innerBottom;
996
- const CGFloat dx = point.x - centerX;
997
- const CGFloat dy = point.y - centerY;
998
- return dx * dx + dy * dy <= radius * radius;
1482
+ path = SmoothClipCreateRoundedRectPath(clip, radii, _normalizedCurve);
1483
+ const BOOL contains = CGPathContainsPoint(path, NULL, point, NO);
1484
+ CGPathRelease(path);
1485
+ return contains;
999
1486
  }
1000
1487
 
1001
- #pragma mark - Legacy native command (Android parity and benchmark baseline)
1488
+ #pragma mark - Native commands
1002
1489
 
1003
1490
  - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
1004
1491
  RCTSmoothClipViewHandleCommand(self, commandName, args);
@@ -1028,6 +1515,39 @@ static std::array<double, 7> SmoothClipVelocityChannels(
1028
1515
  contentTranslateY}];
1029
1516
  }
1030
1517
 
1518
+ - (void)setClipPresentationV2:(double)x
1519
+ y:(double)y
1520
+ width:(double)width
1521
+ height:(double)height
1522
+ topLeftRadius:(double)topLeftRadius
1523
+ topRightRadius:(double)topRightRadius
1524
+ bottomRightRadius:(double)bottomRightRadius
1525
+ bottomLeftRadius:(double)bottomLeftRadius
1526
+ curveCode:(NSInteger)curveCode
1527
+ contentTranslateX:(double)contentTranslateX
1528
+ contentTranslateY:(double)contentTranslateY
1529
+ contentScale:(double)contentScale {
1530
+ smoothclip::Presentation presentation{};
1531
+ if (!SmoothClipBuildPresentationV2(
1532
+ x,
1533
+ y,
1534
+ width,
1535
+ height,
1536
+ topLeftRadius,
1537
+ topRightRadius,
1538
+ bottomRightRadius,
1539
+ bottomLeftRadius,
1540
+ curveCode,
1541
+ contentTranslateX,
1542
+ contentTranslateY,
1543
+ contentScale,
1544
+ &presentation)) {
1545
+ return;
1546
+ }
1547
+ _commandIsAuthoritative = YES;
1548
+ [self smoothClipApplyPresentation:presentation];
1549
+ }
1550
+
1031
1551
  - (void)prepareForRecycle {
1032
1552
  if (_driverId != 0) {
1033
1553
  smoothclip::unregisterView(_driverId, self);
@@ -1041,8 +1561,16 @@ static std::array<double, 7> SmoothClipVelocityChannels(
1041
1561
  _normalizedClip = CGRectZero;
1042
1562
  _requestedRadius = 0;
1043
1563
  _normalizedRadius = 0;
1564
+ _requestedRadii = {0, 0, 0, 0};
1565
+ _normalizedRadii = {0, 0, 0, 0};
1566
+ _requestedCurve = SmoothClipCornerCurveCircular;
1567
+ _normalizedCurve = SmoothClipCornerCurveCircular;
1568
+ _requestedHasExplicitRadii = NO;
1569
+ _normalizedHasExplicitRadii = NO;
1044
1570
  _requestedContentTranslation = CGPointZero;
1045
1571
  _normalizedContentTranslation = CGPointZero;
1572
+ _requestedContentScale = 1;
1573
+ _normalizedContentScale = 1;
1046
1574
  _hasLayout = NO;
1047
1575
  _commandIsAuthoritative = NO;
1048
1576
 
@@ -1050,6 +1578,9 @@ static std::array<double, 7> SmoothClipVelocityChannels(
1050
1578
  layer.bounds = CGRectZero;
1051
1579
  layer.position = CGPointZero;
1052
1580
  layer.cornerRadius = 0;
1581
+ layer.cornerCurve = kCACornerCurveCircular;
1582
+ layer.mask = nil;
1583
+ _unequalCornerMask.path = nil;
1053
1584
  _contentContainer.layer.affineTransform = CGAffineTransformIdentity;
1054
1585
  [self setClipContainerHidden:YES];
1055
1586
  }