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

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 (199) hide show
  1. package/README.md +135 -386
  2. package/android/build.gradle +4 -7
  3. package/android/proguard-rules.pro +2 -2
  4. package/android/src/main/cpp/SmoothClipAndroid.h +9 -12
  5. package/android/src/main/cpp/SmoothClipBindings.cpp +300 -704
  6. package/android/src/main/cpp/SmoothClipRegistry.cpp +348 -594
  7. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +36 -227
  8. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +16 -19
  9. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +36 -294
  10. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +369 -486
  11. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +75 -77
  12. package/cpp/SmoothClipAnimationCurve.h +237 -346
  13. package/cpp/SmoothClipRegistry.h +24 -52
  14. package/cpp/SmoothClipSharedGeometry.h +64 -94
  15. package/cpp/SmoothClipVelocityTracker.h +5 -5
  16. package/ios/SmoothClipGeometry.h +23 -34
  17. package/ios/SmoothClipRegistry.mm +206 -711
  18. package/ios/SmoothClipTurboModule.cpp +217 -715
  19. package/ios/SmoothClipTurboModule.h +54 -297
  20. package/ios/SmoothClipView.mm +599 -505
  21. package/ios/SmoothClipViewRegistry.h +2 -7
  22. package/lib/module/NativeSmoothClipModule.js.map +1 -1
  23. package/lib/module/SmoothClipView.ios.js +84 -17
  24. package/lib/module/SmoothClipView.ios.js.map +1 -1
  25. package/lib/module/SmoothClipView.js +1 -1
  26. package/lib/module/SmoothClipView.js.map +1 -1
  27. package/lib/module/SmoothClipViewNativeComponent.ts +20 -26
  28. package/lib/module/capabilities.native.js +1 -19
  29. package/lib/module/capabilities.native.js.map +1 -1
  30. package/lib/module/capabilityTypes.js +0 -9
  31. package/lib/module/capabilityTypes.js.map +1 -1
  32. package/lib/module/controllerInternals.js +28 -0
  33. package/lib/module/controllerInternals.js.map +1 -0
  34. package/lib/module/controllerLifecycle.js +9 -0
  35. package/lib/module/controllerLifecycle.js.map +1 -0
  36. package/lib/module/controllerTypes.js +4 -0
  37. package/lib/module/controllerTypes.js.map +1 -0
  38. package/lib/module/controllers.android.js +4 -0
  39. package/lib/module/controllers.android.js.map +1 -0
  40. package/lib/module/controllers.ios.js +4 -0
  41. package/lib/module/controllers.ios.js.map +1 -0
  42. package/lib/module/controllers.js +4 -0
  43. package/lib/module/controllers.js.map +1 -0
  44. package/lib/module/controllers.native.js +89 -0
  45. package/lib/module/controllers.native.js.map +1 -0
  46. package/lib/module/geometry.js +71 -51
  47. package/lib/module/geometry.js.map +1 -1
  48. package/lib/module/groupTypes.js +4 -0
  49. package/lib/module/{groupDriverTypes.js.map → groupTypes.js.map} +1 -1
  50. package/lib/module/groups.android.js +4 -0
  51. package/lib/module/groups.android.js.map +1 -0
  52. package/lib/module/groups.ios.js +4 -0
  53. package/lib/module/groups.ios.js.map +1 -0
  54. package/lib/module/groups.js +4 -0
  55. package/lib/module/groups.js.map +1 -0
  56. package/lib/module/groups.native.js +399 -0
  57. package/lib/module/groups.native.js.map +1 -0
  58. package/lib/module/ids.js +8 -0
  59. package/lib/module/ids.js.map +1 -0
  60. package/lib/module/index.js +3 -3
  61. package/lib/module/index.js.map +1 -1
  62. package/lib/module/nativeCompletion.js +37 -16
  63. package/lib/module/nativeCompletion.js.map +1 -1
  64. package/lib/module/presentationCodec.js +81 -0
  65. package/lib/module/presentationCodec.js.map +1 -0
  66. package/lib/typescript/src/NativeSmoothClipModule.d.ts +14 -23
  67. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  68. package/lib/typescript/src/SmoothClipView.d.ts +1 -1
  69. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  70. package/lib/typescript/src/SmoothClipView.ios.d.ts +18 -14
  71. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  72. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +10 -5
  73. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  74. package/lib/typescript/src/capabilities.native.d.ts +1 -1
  75. package/lib/typescript/src/capabilities.native.d.ts.map +1 -1
  76. package/lib/typescript/src/capabilityTypes.d.ts +0 -6
  77. package/lib/typescript/src/capabilityTypes.d.ts.map +1 -1
  78. package/lib/typescript/src/controllerInternals.d.ts +14 -0
  79. package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
  80. package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
  81. package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
  82. package/lib/typescript/src/controllerTypes.d.ts +62 -0
  83. package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
  84. package/lib/typescript/src/controllers.android.d.ts +2 -0
  85. package/lib/typescript/src/controllers.android.d.ts.map +1 -0
  86. package/lib/typescript/src/controllers.d.ts +2 -0
  87. package/lib/typescript/src/controllers.d.ts.map +1 -0
  88. package/lib/typescript/src/controllers.ios.d.ts +2 -0
  89. package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
  90. package/lib/typescript/src/controllers.native.d.ts +3 -0
  91. package/lib/typescript/src/controllers.native.d.ts.map +1 -0
  92. package/lib/typescript/src/geometry.d.ts +22 -12
  93. package/lib/typescript/src/geometry.d.ts.map +1 -1
  94. package/lib/typescript/src/groupTypes.d.ts +31 -0
  95. package/lib/typescript/src/groupTypes.d.ts.map +1 -0
  96. package/lib/typescript/src/groups.android.d.ts +2 -0
  97. package/lib/typescript/src/groups.android.d.ts.map +1 -0
  98. package/lib/typescript/src/groups.d.ts +2 -0
  99. package/lib/typescript/src/groups.d.ts.map +1 -0
  100. package/lib/typescript/src/groups.ios.d.ts +2 -0
  101. package/lib/typescript/src/groups.ios.d.ts.map +1 -0
  102. package/lib/typescript/src/groups.native.d.ts +3 -0
  103. package/lib/typescript/src/groups.native.d.ts.map +1 -0
  104. package/lib/typescript/src/ids.d.ts +2 -0
  105. package/lib/typescript/src/ids.d.ts.map +1 -0
  106. package/lib/typescript/src/index.d.ts +6 -6
  107. package/lib/typescript/src/index.d.ts.map +1 -1
  108. package/lib/typescript/src/nativeCompletion.d.ts +16 -6
  109. package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
  110. package/lib/typescript/src/presentationCodec.d.ts +10 -0
  111. package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
  112. package/package.json +8 -3
  113. package/src/NativeSmoothClipModule.ts +41 -292
  114. package/src/SmoothClipView.ios.tsx +130 -23
  115. package/src/SmoothClipView.tsx +5 -1
  116. package/src/SmoothClipViewNativeComponent.ts +20 -26
  117. package/src/capabilities.native.ts +2 -52
  118. package/src/capabilityTypes.ts +0 -14
  119. package/src/controllerInternals.ts +52 -0
  120. package/src/controllerLifecycle.ts +6 -0
  121. package/src/controllerTypes.ts +87 -0
  122. package/src/controllers.android.ts +1 -0
  123. package/src/controllers.ios.ts +1 -0
  124. package/src/controllers.native.ts +104 -0
  125. package/src/controllers.ts +1 -0
  126. package/src/geometry.ts +118 -74
  127. package/src/groupTypes.ts +53 -0
  128. package/src/groups.android.ts +1 -0
  129. package/src/groups.ios.ts +1 -0
  130. package/src/groups.native.ts +604 -0
  131. package/src/groups.ts +1 -0
  132. package/src/ids.ts +6 -0
  133. package/src/index.ts +20 -28
  134. package/src/nativeCompletion.ts +67 -32
  135. package/src/presentationCodec.ts +134 -0
  136. package/lib/module/driverState.js +0 -82
  137. package/lib/module/driverState.js.map +0 -1
  138. package/lib/module/driverTypes.js +0 -4
  139. package/lib/module/driverTypes.js.map +0 -1
  140. package/lib/module/drivers.android.js +0 -6
  141. package/lib/module/drivers.android.js.map +0 -1
  142. package/lib/module/drivers.ios.js +0 -6
  143. package/lib/module/drivers.ios.js.map +0 -1
  144. package/lib/module/drivers.js +0 -4
  145. package/lib/module/drivers.js.map +0 -1
  146. package/lib/module/drivers.native.js +0 -756
  147. package/lib/module/drivers.native.js.map +0 -1
  148. package/lib/module/groupDriverTypes.js +0 -4
  149. package/lib/module/groupDrivers.android.js +0 -4
  150. package/lib/module/groupDrivers.android.js.map +0 -1
  151. package/lib/module/groupDrivers.ios.js +0 -4
  152. package/lib/module/groupDrivers.ios.js.map +0 -1
  153. package/lib/module/groupDrivers.js +0 -4
  154. package/lib/module/groupDrivers.js.map +0 -1
  155. package/lib/module/groupDrivers.native.js +0 -799
  156. package/lib/module/groupDrivers.native.js.map +0 -1
  157. package/lib/module/presentationProtocol.js +0 -15
  158. package/lib/module/presentationProtocol.js.map +0 -1
  159. package/lib/module/reactRequests.js +0 -56
  160. package/lib/module/reactRequests.js.map +0 -1
  161. package/lib/typescript/src/driverState.d.ts +0 -26
  162. package/lib/typescript/src/driverState.d.ts.map +0 -1
  163. package/lib/typescript/src/driverTypes.d.ts +0 -121
  164. package/lib/typescript/src/driverTypes.d.ts.map +0 -1
  165. package/lib/typescript/src/drivers.android.d.ts +0 -3
  166. package/lib/typescript/src/drivers.android.d.ts.map +0 -1
  167. package/lib/typescript/src/drivers.d.ts +0 -3
  168. package/lib/typescript/src/drivers.d.ts.map +0 -1
  169. package/lib/typescript/src/drivers.ios.d.ts +0 -3
  170. package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
  171. package/lib/typescript/src/drivers.native.d.ts +0 -5
  172. package/lib/typescript/src/drivers.native.d.ts.map +0 -1
  173. package/lib/typescript/src/groupDriverTypes.d.ts +0 -66
  174. package/lib/typescript/src/groupDriverTypes.d.ts.map +0 -1
  175. package/lib/typescript/src/groupDrivers.android.d.ts +0 -2
  176. package/lib/typescript/src/groupDrivers.android.d.ts.map +0 -1
  177. package/lib/typescript/src/groupDrivers.d.ts +0 -2
  178. package/lib/typescript/src/groupDrivers.d.ts.map +0 -1
  179. package/lib/typescript/src/groupDrivers.ios.d.ts +0 -2
  180. package/lib/typescript/src/groupDrivers.ios.d.ts.map +0 -1
  181. package/lib/typescript/src/groupDrivers.native.d.ts +0 -4
  182. package/lib/typescript/src/groupDrivers.native.d.ts.map +0 -1
  183. package/lib/typescript/src/presentationProtocol.d.ts +0 -4
  184. package/lib/typescript/src/presentationProtocol.d.ts.map +0 -1
  185. package/lib/typescript/src/reactRequests.d.ts +0 -9
  186. package/lib/typescript/src/reactRequests.d.ts.map +0 -1
  187. package/src/driverState.ts +0 -118
  188. package/src/driverTypes.ts +0 -171
  189. package/src/drivers.android.ts +0 -8
  190. package/src/drivers.ios.ts +0 -8
  191. package/src/drivers.native.ts +0 -1599
  192. package/src/drivers.ts +0 -6
  193. package/src/groupDriverTypes.ts +0 -117
  194. package/src/groupDrivers.android.ts +0 -1
  195. package/src/groupDrivers.ios.ts +0 -1
  196. package/src/groupDrivers.native.ts +0 -1189
  197. package/src/groupDrivers.ts +0 -1
  198. package/src/presentationProtocol.ts +0 -28
  199. package/src/reactRequests.ts +0 -76
@@ -18,9 +18,7 @@ struct Geometry {
18
18
  double width;
19
19
  double height;
20
20
  double radius;
21
- // NaN means "use radius". Keeping the overrides at the end preserves every
22
- // existing five-value aggregate initializer used by the V1 implementation
23
- // and its native tests.
21
+ // NaN means "use radius", allowing concise uniform-radius presentations.
24
22
  double topLeftRadius = std::numeric_limits<double>::quiet_NaN();
25
23
  double topRightRadius = std::numeric_limits<double>::quiet_NaN();
26
24
  double bottomRightRadius = std::numeric_limits<double>::quiet_NaN();
@@ -28,16 +26,24 @@ struct Geometry {
28
26
  ClipCurve curve = ClipCurve::Circular;
29
27
  };
30
28
 
29
+ struct Shadow {
30
+ bool enabled = false;
31
+ double red = 0;
32
+ double green = 0;
33
+ double blue = 0;
34
+ double alpha = 1;
35
+ double offsetX = 0;
36
+ double offsetY = 0;
37
+ double blurRadius = 0;
38
+ double spreadDistance = 0;
39
+ };
40
+
31
41
  struct Presentation {
32
42
  Geometry clip;
33
43
  double contentTranslateX;
34
44
  double contentTranslateY;
35
45
  double contentScale = 1.0;
36
- };
37
-
38
- struct Keyframe {
39
- double offset;
40
- Presentation presentation;
46
+ Shadow shadow{};
41
47
  };
42
48
 
43
49
  struct TimingAnimation {
@@ -56,6 +62,7 @@ struct SpringAnimation {
56
62
  double initialVelocity;
57
63
  bool inheritVelocity;
58
64
  int32_t reduceMotion;
65
+ double energyThreshold = 6e-9;
59
66
  };
60
67
 
61
68
  struct AnimationStart {
@@ -92,12 +99,6 @@ struct GroupMotionEntry {
92
99
  bool hasFrom;
93
100
  Presentation from;
94
101
  Presentation target;
95
- std::vector<Keyframe> keyframes;
96
- };
97
-
98
- enum class GroupSuspensionPolicy : int32_t {
99
- Pause = 0,
100
- Finish = 1,
101
102
  };
102
103
 
103
104
  enum class GroupCancelBehavior : int32_t {
@@ -105,19 +106,11 @@ enum class GroupCancelBehavior : int32_t {
105
106
  Finish = 1,
106
107
  };
107
108
 
108
- // The seven-scalar V1 bridge predates protocol-V2 host-normalization
109
- // preflight and strict spring/duration rejection. Keep that wire contract
110
- // selectable without widening any V1 entry point; V2 and grouped calls retain
111
- // the conservative validation required for portable native ownership.
112
- enum class AnimationValidationMode : int32_t {
113
- LegacyV1 = 1,
114
- ProtocolV2 = 2,
115
- };
116
-
117
109
  using CompletionCallback =
118
- std::function<void(uint64_t, int32_t, bool)>;
110
+ std::function<void(uint64_t, int32_t, int32_t, bool)>;
119
111
  using GroupCompletionCallback =
120
- std::function<void(uint64_t, int32_t, bool, std::vector<uint64_t>)>;
112
+ std::function<
113
+ void(uint64_t, int32_t, int32_t, bool, std::vector<DriverSnapshot>)>;
121
114
 
122
115
  void setCompletionCallback(
123
116
  const void *owner,
@@ -128,11 +121,8 @@ void setGroupCompletionCallback(
128
121
  GroupCompletionCallback callback);
129
122
  void clearGroupCompletionCallback(const void *owner);
130
123
 
131
- // recordVelocity gates the 'inherit' velocity-sample recording for this write.
132
- // Default true (the pre-flag behavior); Android's JS layer passes false for
133
- // setScalars hot writes on drivers without SmoothClipDriverOptions
134
- // .velocityTracking, so the per-frame drag stream skips the clock read and
135
- // channel copies. iOS callers never pass it and keep recording always.
124
+ // Interactive writes always keep the two samples needed by a later
125
+ // `initialVelocity: "inherit"` spring.
136
126
  void setPresentation(
137
127
  uint64_t driverId,
138
128
  Presentation presentation,
@@ -150,20 +140,13 @@ int32_t animateTimingGroup(
150
140
  uint64_t controllerId,
151
141
  std::vector<GroupMotionEntry> entries,
152
142
  TimingAnimation animation,
153
- GroupSuspensionPolicy suspensionPolicy,
143
+ int32_t completionTag = 0,
154
144
  double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
155
145
  int32_t animateSpringGroup(
156
146
  uint64_t controllerId,
157
147
  std::vector<GroupMotionEntry> entries,
158
148
  SpringAnimation animation,
159
- GroupSuspensionPolicy suspensionPolicy,
160
- double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
161
- int32_t animateKeyframesGroup(
162
- uint64_t controllerId,
163
- std::vector<GroupMotionEntry> entries,
164
- double durationMs,
165
- int32_t reduceMotion,
166
- GroupSuspensionPolicy suspensionPolicy,
149
+ int32_t completionTag = 0,
167
150
  double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
168
151
  std::vector<DriverSnapshot> cancelAnimationGroup(
169
152
  int32_t groupId,
@@ -173,24 +156,13 @@ int32_t animateTiming(
173
156
  AnimationStart start,
174
157
  Presentation presentation,
175
158
  TimingAnimation animation,
176
- AnimationValidationMode validationMode =
177
- AnimationValidationMode::ProtocolV2);
159
+ int32_t completionTag = 0);
178
160
  int32_t animateSpring(
179
161
  uint64_t driverId,
180
162
  AnimationStart start,
181
163
  Presentation presentation,
182
164
  SpringAnimation animation,
183
- AnimationValidationMode validationMode =
184
- AnimationValidationMode::ProtocolV2);
185
- int32_t animateKeyframes(
186
- uint64_t driverId,
187
- AnimationStart start,
188
- Presentation presentation,
189
- double durationMs,
190
- std::vector<Keyframe> keyframes,
191
- int32_t reduceMotion,
192
- AnimationValidationMode validationMode =
193
- AnimationValidationMode::ProtocolV2);
165
+ int32_t completionTag = 0);
194
166
  int32_t rejectAnimation(uint64_t driverId);
195
167
  CancelResult cancelAnimation(
196
168
  uint64_t driverId,
@@ -7,7 +7,13 @@
7
7
 
8
8
  namespace smoothclip {
9
9
 
10
- struct NormalizedClip {
10
+ /**
11
+ * Host-independent canonical clip geometry expressed as edges.
12
+ *
13
+ * The moving aperture is allowed to cross or sit outside any host. Hosts crop
14
+ * the rendered result; they never rewrite the geometry stored by the driver.
15
+ */
16
+ struct CanonicalClip {
11
17
  double left = 0;
12
18
  double top = 0;
13
19
  double right = 0;
@@ -25,71 +31,13 @@ inline double SmoothClipResolvedRadius(double overrideValue, double radius) {
25
31
  }
26
32
 
27
33
  /**
28
- * Returns true only when host normalization is the identity for this geometry.
29
- *
30
- * Timing/keyframe renderers interpolate already-normalized native values on
31
- * iOS, while Android interpolates presentation scalars and normalizes each
32
- * delivered frame. Those operations commute exactly when every anchor stays
33
- * inside one host and the CSS corner-overlap rule never activates. Because
34
- * each condition below is linear, it also holds for every convex interpolation
35
- * between accepted anchors.
34
+ * Canonicalizes a requested rectangle without consulting host dimensions.
36
35
  *
37
- * Static clipping deliberately does not use this gate: out-of-bounds geometry
38
- * remains valid and is still clipped by SmoothClipNormalizeV2.
36
+ * Negative dimensions collapse to zero. Corner radii follow the CSS
37
+ * corner-overlap rule against the requested rectangle, using one common scale
38
+ * factor so their proportions are preserved.
39
39
  */
40
- inline bool SmoothClipGeometryNormalizationIsIdentity(
41
- const Geometry &geometry,
42
- double hostWidth,
43
- double hostHeight) {
44
- const double topLeft = SmoothClipResolvedRadius(
45
- geometry.topLeftRadius, geometry.radius);
46
- const double topRight = SmoothClipResolvedRadius(
47
- geometry.topRightRadius, geometry.radius);
48
- const double bottomRight = SmoothClipResolvedRadius(
49
- geometry.bottomRightRadius, geometry.radius);
50
- const double bottomLeft = SmoothClipResolvedRadius(
51
- geometry.bottomLeftRadius, geometry.radius);
52
- const double right = geometry.x + geometry.width;
53
- const double bottom = geometry.y + geometry.height;
54
- const double topRadii = topLeft + topRight;
55
- const double bottomRadii = bottomLeft + bottomRight;
56
- const double leftRadii = topLeft + bottomLeft;
57
- const double rightRadii = topRight + bottomRight;
58
- const double values[] = {
59
- geometry.x,
60
- geometry.y,
61
- geometry.width,
62
- geometry.height,
63
- geometry.radius,
64
- topLeft,
65
- topRight,
66
- bottomRight,
67
- bottomLeft,
68
- right,
69
- bottom,
70
- topRadii,
71
- bottomRadii,
72
- leftRadii,
73
- rightRadii,
74
- hostWidth,
75
- hostHeight,
76
- };
77
- for (const double value : values) {
78
- if (!std::isfinite(value)) return false;
79
- }
80
- return hostWidth >= 0 && hostHeight >= 0 &&
81
- geometry.x >= 0 && geometry.y >= 0 &&
82
- geometry.width >= 0 && geometry.height >= 0 &&
83
- right <= hostWidth && bottom <= hostHeight &&
84
- topLeft >= 0 && topRight >= 0 &&
85
- bottomRight >= 0 && bottomLeft >= 0 &&
86
- topRadii <= geometry.width &&
87
- bottomRadii <= geometry.width &&
88
- leftRadii <= geometry.height &&
89
- rightRadii <= geometry.height;
90
- }
91
-
92
- inline bool SmoothClipNormalizeV2(
40
+ inline bool SmoothClipCanonicalize(
93
41
  double x,
94
42
  double y,
95
43
  double width,
@@ -100,9 +48,7 @@ inline bool SmoothClipNormalizeV2(
100
48
  double bottomRightRadius,
101
49
  double bottomLeftRadius,
102
50
  ClipCurve curve,
103
- double hostWidth,
104
- double hostHeight,
105
- NormalizedClip &out) {
51
+ CanonicalClip &out) {
106
52
  const double resolvedTopLeft =
107
53
  SmoothClipResolvedRadius(topLeftRadius, radius);
108
54
  const double resolvedTopRight =
@@ -117,20 +63,19 @@ inline bool SmoothClipNormalizeV2(
117
63
  !std::isfinite(resolvedTopRight) ||
118
64
  !std::isfinite(resolvedBottomRight) ||
119
65
  !std::isfinite(resolvedBottomLeft) ||
120
- !std::isfinite(hostWidth) || !std::isfinite(hostHeight)) {
66
+ (curve != ClipCurve::Circular && curve != ClipCurve::Continuous)) {
121
67
  return false;
122
68
  }
123
69
 
124
- const double boundedHostWidth = std::max(0.0, hostWidth);
125
- const double boundedHostHeight = std::max(0.0, hostHeight);
126
- const double requestedRight = x + std::max(0.0, width);
127
- const double requestedBottom = y + std::max(0.0, height);
128
- out.left = std::min(boundedHostWidth, std::max(0.0, x));
129
- out.top = std::min(boundedHostHeight, std::max(0.0, y));
130
- out.right = std::min(boundedHostWidth, std::max(0.0, requestedRight));
131
- out.bottom = std::min(boundedHostHeight, std::max(0.0, requestedBottom));
132
- const double visibleWidth = std::max(0.0, out.right - out.left);
133
- const double visibleHeight = std::max(0.0, out.bottom - out.top);
70
+ const double canonicalWidth = std::max(0.0, width);
71
+ const double canonicalHeight = std::max(0.0, height);
72
+ const double right = x + canonicalWidth;
73
+ const double bottom = y + canonicalHeight;
74
+ if (!std::isfinite(right) || !std::isfinite(bottom)) return false;
75
+ out.left = x;
76
+ out.top = y;
77
+ out.right = right;
78
+ out.bottom = bottom;
134
79
 
135
80
  double topLeft = std::max(0.0, resolvedTopLeft);
136
81
  double topRight = std::max(0.0, resolvedTopRight);
@@ -140,11 +85,12 @@ inline bool SmoothClipNormalizeV2(
140
85
  const auto includeLimit = [&scale](double available, double requested) {
141
86
  if (requested > 0) scale = std::min(scale, available / requested);
142
87
  };
143
- includeLimit(visibleWidth, topLeft + topRight);
144
- includeLimit(visibleWidth, bottomLeft + bottomRight);
145
- includeLimit(visibleHeight, topLeft + bottomLeft);
146
- includeLimit(visibleHeight, topRight + bottomRight);
147
- scale = std::max(0.0, std::min(1.0, scale));
88
+ includeLimit(canonicalWidth, topLeft + topRight);
89
+ includeLimit(canonicalWidth, bottomLeft + bottomRight);
90
+ includeLimit(canonicalHeight, topLeft + bottomLeft);
91
+ includeLimit(canonicalHeight, topRight + bottomRight);
92
+ scale = std::clamp(scale, 0.0, 1.0);
93
+
148
94
  out.topLeftRadius = topLeft * scale;
149
95
  out.topRightRadius = topRight * scale;
150
96
  out.bottomRightRadius = bottomRight * scale;
@@ -157,20 +103,31 @@ inline bool SmoothClipNormalizeV2(
157
103
  return true;
158
104
  }
159
105
 
160
- // Shared clip normalization emitting edges (Android's Outline consumes
161
- // edges). Mirrors ios/SmoothClipGeometry.h (SmoothClipNormalizeGeometry) and
162
- // ClipGeometryNormalizer.kt (normalizeClipGeometryPx); a change here must
163
- // land in all three.
164
- inline bool SmoothClipNormalize(
106
+ inline bool SmoothClipCanonicalize(
107
+ const Geometry &geometry,
108
+ CanonicalClip &out) {
109
+ return SmoothClipCanonicalize(
110
+ geometry.x,
111
+ geometry.y,
112
+ geometry.width,
113
+ geometry.height,
114
+ geometry.radius,
115
+ geometry.topLeftRadius,
116
+ geometry.topRightRadius,
117
+ geometry.bottomRightRadius,
118
+ geometry.bottomLeftRadius,
119
+ geometry.curve,
120
+ out);
121
+ }
122
+
123
+ inline bool SmoothClipCanonicalize(
165
124
  double x,
166
125
  double y,
167
126
  double width,
168
127
  double height,
169
128
  double radius,
170
- double hostWidth,
171
- double hostHeight,
172
- NormalizedClip &out) {
173
- return SmoothClipNormalizeV2(
129
+ CanonicalClip &out) {
130
+ return SmoothClipCanonicalize(
174
131
  x,
175
132
  y,
176
133
  width,
@@ -181,9 +138,22 @@ inline bool SmoothClipNormalize(
181
138
  radius,
182
139
  radius,
183
140
  ClipCurve::Circular,
184
- hostWidth,
185
- hostHeight,
186
141
  out);
187
142
  }
188
143
 
144
+ inline Geometry SmoothClipGeometry(const CanonicalClip &clip) {
145
+ Geometry geometry{
146
+ clip.left,
147
+ clip.top,
148
+ std::max(0.0, clip.right - clip.left),
149
+ std::max(0.0, clip.bottom - clip.top),
150
+ clip.radius};
151
+ geometry.topLeftRadius = clip.topLeftRadius;
152
+ geometry.topRightRadius = clip.topRightRadius;
153
+ geometry.bottomRightRadius = clip.bottomRightRadius;
154
+ geometry.bottomLeftRadius = clip.bottomLeftRadius;
155
+ geometry.curve = clip.curve;
156
+ return geometry;
157
+ }
158
+
189
159
  } // namespace smoothclip
@@ -6,11 +6,11 @@
6
6
  namespace smoothclip {
7
7
 
8
8
  // The two most recent interactive samples used by `initialVelocity: 'inherit'`.
9
- // Channels are the eleven continuous V2 presentation scalars. Scale is stored
9
+ // Channels are the eleven geometry/content presentation scalars. Scale is stored
10
10
  // so histories remain coherent but excluded from velocity projection; each of
11
- // the four radius channels contributes one quarter of the legacy radius weight.
12
- // iOS records normalized (host-clamped) geometry, Android raw DIP — the
13
- // projection only needs both samples and the target in the same space.
11
+ // the four radius channels contributes one quarter of the uniform-radius weight.
12
+ // Both platforms record raw canonical geometry, including off-host coordinates.
13
+ // The projection only needs both samples and the target in the same space.
14
14
  // Shared by ios/SmoothClipView.mm and android/.../SmoothClipRegistry.cpp; a
15
15
  // behavior change here changes both platforms.
16
16
  struct VelocitySampleHistory {
@@ -69,7 +69,7 @@ inline void recordVelocitySample(
69
69
  }
70
70
 
71
71
  // Only explicit interactive writes route through this recorder. Native
72
- // freeze/join/resume/static-finalization paths deliberately do not, so an
72
+ // Native freeze and endpoint-finalization paths deliberately do not, so an
73
73
  // animation cannot manufacture a later inherited velocity. During ordinary
74
74
  // interaction, the staleness decay below is the forgetting mechanism and it
75
75
  // forgets gradually rather than all at once.
@@ -16,12 +16,11 @@ typedef NS_ENUM(NSInteger, SmoothClipCornerCurve) {
16
16
 
17
17
  typedef struct {
18
18
  CGRect rect;
19
- // Retained for the V1 uniform-radius contract. V2 callers should consume
20
- // `radii`; this field is zero when the normalized radii are unequal.
19
+ // Uniform-radius fast-path value; zero when canonical radii are unequal.
21
20
  CGFloat radius;
22
21
  SmoothClipCornerRadii radii;
23
22
  SmoothClipCornerCurve curve;
24
- } SmoothNormalizedClipGeometry;
23
+ } SmoothClipCanonicalGeometry;
25
24
 
26
25
  NS_INLINE BOOL SmoothClipCornerRadiiEqual(
27
26
  SmoothClipCornerRadii first,
@@ -48,13 +47,13 @@ NS_INLINE CGFloat SmoothClipSafeOverlapFactor(
48
47
  }
49
48
 
50
49
  /**
51
- * Normalizes the V2 rectangle and four radii without allocating.
50
+ * Canonicalizes the rectangle and four radii without allocating.
52
51
  *
53
52
  * The proportional overlap rule matches CSS border radii and the shared
54
53
  * Android/C++ contract: all corners use one scale factor, preserving their
55
54
  * relative shape while no opposing pair exceeds its edge.
56
55
  */
57
- NS_INLINE BOOL SmoothClipNormalizeGeometryV2(
56
+ NS_INLINE BOOL SmoothClipCanonicalizeGeometry(
58
57
  CGFloat x,
59
58
  CGFloat y,
60
59
  CGFloat width,
@@ -64,30 +63,21 @@ NS_INLINE BOOL SmoothClipNormalizeGeometryV2(
64
63
  CGFloat bottomRightRadius,
65
64
  CGFloat bottomLeftRadius,
66
65
  NSInteger curveCode,
67
- CGSize hostSize,
68
- SmoothNormalizedClipGeometry *result) {
66
+ SmoothClipCanonicalGeometry *result) {
69
67
  if (!isfinite(x) || !isfinite(y) || !isfinite(width) ||
70
68
  !isfinite(height) || !isfinite(topLeftRadius) ||
71
69
  !isfinite(topRightRadius) || !isfinite(bottomRightRadius) ||
72
- !isfinite(bottomLeftRadius) || !isfinite(hostSize.width) ||
73
- !isfinite(hostSize.height) ||
70
+ !isfinite(bottomLeftRadius) ||
74
71
  (curveCode != SmoothClipCornerCurveCircular &&
75
72
  curveCode != SmoothClipCornerCurveContinuous)) {
76
73
  return NO;
77
74
  }
78
75
 
79
- const CGFloat hostWidth = MAX(0, hostSize.width);
80
- const CGFloat hostHeight = MAX(0, hostSize.height);
81
76
  const CGFloat requestedWidth = MAX(0, width);
82
77
  const CGFloat requestedHeight = MAX(0, height);
83
- const CGFloat requestedRight = x + requestedWidth;
84
- const CGFloat requestedBottom = y + requestedHeight;
85
- const CGFloat left = MIN(hostWidth, MAX(0, x));
86
- const CGFloat top = MIN(hostHeight, MAX(0, y));
87
- const CGFloat right = MIN(hostWidth, MAX(0, requestedRight));
88
- const CGFloat bottom = MIN(hostHeight, MAX(0, requestedBottom));
89
- const CGFloat visibleWidth = MAX(0, right - left);
90
- const CGFloat visibleHeight = MAX(0, bottom - top);
78
+ if (!isfinite(x + requestedWidth) || !isfinite(y + requestedHeight)) {
79
+ return NO;
80
+ }
91
81
 
92
82
  SmoothClipCornerRadii radii = {
93
83
  MAX(0, topLeftRadius),
@@ -100,20 +90,20 @@ NS_INLINE BOOL SmoothClipNormalizeGeometryV2(
100
90
  MIN(
101
91
  MIN(
102
92
  SmoothClipSafeOverlapFactor(
103
- visibleWidth, radii.topLeft, radii.topRight),
93
+ requestedWidth, radii.topLeft, radii.topRight),
104
94
  SmoothClipSafeOverlapFactor(
105
- visibleWidth, radii.bottomLeft, radii.bottomRight)),
95
+ requestedWidth, radii.bottomLeft, radii.bottomRight)),
106
96
  MIN(
107
97
  SmoothClipSafeOverlapFactor(
108
- visibleHeight, radii.topLeft, radii.bottomLeft),
98
+ requestedHeight, radii.topLeft, radii.bottomLeft),
109
99
  SmoothClipSafeOverlapFactor(
110
- visibleHeight, radii.topRight, radii.bottomRight))));
100
+ requestedHeight, radii.topRight, radii.bottomRight))));
111
101
  radii.topLeft *= overlapScale;
112
102
  radii.topRight *= overlapScale;
113
103
  radii.bottomRight *= overlapScale;
114
104
  radii.bottomLeft *= overlapScale;
115
105
 
116
- result->rect = CGRectMake(left, top, visibleWidth, visibleHeight);
106
+ result->rect = CGRectMake(x, y, requestedWidth, requestedHeight);
117
107
  result->radii = radii;
118
108
  result->radius = SmoothClipCornerRadiiAreUniform(radii)
119
109
  ? radii.topLeft
@@ -123,15 +113,14 @@ NS_INLINE BOOL SmoothClipNormalizeGeometryV2(
123
113
  }
124
114
 
125
115
  /** Mirrors the Android and JavaScript geometry contract without allocating. */
126
- NS_INLINE BOOL SmoothClipNormalizeGeometry(
116
+ NS_INLINE BOOL SmoothClipCanonicalizeGeometry(
127
117
  CGFloat x,
128
118
  CGFloat y,
129
119
  CGFloat width,
130
120
  CGFloat height,
131
121
  CGFloat radius,
132
- CGSize hostSize,
133
- SmoothNormalizedClipGeometry *result) {
134
- return SmoothClipNormalizeGeometryV2(
122
+ SmoothClipCanonicalGeometry *result) {
123
+ return SmoothClipCanonicalizeGeometry(
135
124
  x,
136
125
  y,
137
126
  width,
@@ -141,20 +130,21 @@ NS_INLINE BOOL SmoothClipNormalizeGeometry(
141
130
  radius,
142
131
  radius,
143
132
  SmoothClipCornerCurveCircular,
144
- hostSize,
145
133
  result);
146
134
  }
147
135
 
148
136
  /**
149
- * Creates the stable nine-segment rounded-rectangle topology used by the
150
- * unequal-corner CAShapeLayer mask. Every corner is one cubic, including a
151
- * zero-radius corner, so Core Animation can interpolate paths without a
152
- * topology change.
137
+ * Creates an empty path for an empty clip. Non-empty rounded rectangles use a
138
+ * stable nine-segment topology. Every corner is one cubic, including a
139
+ * zero-radius corner, so Core Animation can interpolate non-empty paths
140
+ * without a topology change.
153
141
  */
154
142
  NS_INLINE CGPathRef SmoothClipCreateRoundedRectPath(
155
143
  CGRect rect,
156
144
  SmoothClipCornerRadii radii,
157
145
  SmoothClipCornerCurve curve) CF_RETURNS_RETAINED {
146
+ CGMutablePathRef path = CGPathCreateMutable();
147
+ if (CGRectIsEmpty(rect)) return path;
158
148
  const CGFloat minX = CGRectGetMinX(rect);
159
149
  const CGFloat minY = CGRectGetMinY(rect);
160
150
  const CGFloat maxX = CGRectGetMaxX(rect);
@@ -171,7 +161,6 @@ NS_INLINE CGPathRef SmoothClipCreateRoundedRectPath(
171
161
  ? 1.0
172
162
  : 0.5522847498307936;
173
163
 
174
- CGMutablePathRef path = CGPathCreateMutable();
175
164
  CGPathMoveToPoint(path, NULL, minX + radii.topLeft, minY);
176
165
  CGPathAddLineToPoint(path, NULL, maxX - radii.topRight, minY);
177
166
  CGPathAddCurveToPoint(