react-native-smooth-clip-view 0.2.9 → 0.4.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 (193) hide show
  1. package/README.md +132 -349
  2. package/android/build.gradle +8 -4
  3. package/android/proguard-rules.pro +3 -3
  4. package/android/src/main/cpp/SmoothClipAndroid.h +13 -6
  5. package/android/src/main/cpp/SmoothClipBindings.cpp +605 -123
  6. package/android/src/main/cpp/SmoothClipRegistry.cpp +975 -209
  7. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +192 -118
  8. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +22 -4
  9. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +81 -56
  10. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +449 -240
  11. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +150 -38
  12. package/cpp/SmoothClipAnimationCurve.h +317 -397
  13. package/cpp/SmoothClipRegistry.h +84 -20
  14. package/cpp/SmoothClipSharedGeometry.h +131 -22
  15. package/cpp/SmoothClipVelocityTracker.h +16 -12
  16. package/ios/SmoothClipGeometry.h +188 -21
  17. package/ios/SmoothClipRegistry.mm +869 -368
  18. package/ios/SmoothClipRegistrySnapshot.h +13 -0
  19. package/ios/SmoothClipTurboModule.cpp +513 -156
  20. package/ios/SmoothClipTurboModule.h +123 -56
  21. package/ios/SmoothClipView.mm +1074 -449
  22. package/ios/SmoothClipViewRegistry.h +3 -7
  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 +94 -11
  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 +32 -12
  31. package/lib/module/capabilities.js +4 -0
  32. package/lib/module/capabilities.js.map +1 -0
  33. package/lib/module/capabilities.native.js +18 -0
  34. package/lib/module/capabilities.native.js.map +1 -0
  35. package/lib/module/capabilityTypes.js +2 -0
  36. package/lib/module/capabilityTypes.js.map +1 -0
  37. package/lib/module/controllerInternals.js +28 -0
  38. package/lib/module/controllerInternals.js.map +1 -0
  39. package/lib/module/controllerLifecycle.js +9 -0
  40. package/lib/module/controllerLifecycle.js.map +1 -0
  41. package/lib/module/controllerTypes.js +4 -0
  42. package/lib/module/controllerTypes.js.map +1 -0
  43. package/lib/module/controllers.android.js +4 -0
  44. package/lib/module/controllers.android.js.map +1 -0
  45. package/lib/module/controllers.ios.js +4 -0
  46. package/lib/module/controllers.ios.js.map +1 -0
  47. package/lib/module/controllers.js +4 -0
  48. package/lib/module/controllers.js.map +1 -0
  49. package/lib/module/controllers.native.js +89 -0
  50. package/lib/module/controllers.native.js.map +1 -0
  51. package/lib/module/geometry.js +180 -30
  52. package/lib/module/geometry.js.map +1 -1
  53. package/lib/module/groupTypes.js +4 -0
  54. package/lib/module/groupTypes.js.map +1 -0
  55. package/lib/module/groups.android.js +4 -0
  56. package/lib/module/groups.android.js.map +1 -0
  57. package/lib/module/groups.ios.js +4 -0
  58. package/lib/module/groups.ios.js.map +1 -0
  59. package/lib/module/groups.js +4 -0
  60. package/lib/module/groups.js.map +1 -0
  61. package/lib/module/groups.native.js +399 -0
  62. package/lib/module/groups.native.js.map +1 -0
  63. package/lib/module/ids.js +8 -0
  64. package/lib/module/ids.js.map +1 -0
  65. package/lib/module/index.js +4 -3
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/module/nativeCompletion.js +37 -16
  68. package/lib/module/nativeCompletion.js.map +1 -1
  69. package/lib/module/presentationCodec.js +81 -0
  70. package/lib/module/presentationCodec.js.map +1 -0
  71. package/lib/typescript/src/NativeSmoothClipModule.d.ts +20 -4
  72. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  73. package/lib/typescript/src/SmoothClipView.d.ts +2 -10
  74. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  75. package/lib/typescript/src/SmoothClipView.ios.d.ts +139 -4
  76. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  77. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +16 -3
  78. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  79. package/lib/typescript/src/capabilities.d.ts +3 -0
  80. package/lib/typescript/src/capabilities.d.ts.map +1 -0
  81. package/lib/typescript/src/capabilities.native.d.ts +3 -0
  82. package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
  83. package/lib/typescript/src/capabilityTypes.d.ts +4 -0
  84. package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
  85. package/lib/typescript/src/controllerInternals.d.ts +14 -0
  86. package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
  87. package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
  88. package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
  89. package/lib/typescript/src/controllerTypes.d.ts +62 -0
  90. package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
  91. package/lib/typescript/src/controllers.android.d.ts +2 -0
  92. package/lib/typescript/src/controllers.android.d.ts.map +1 -0
  93. package/lib/typescript/src/controllers.d.ts +2 -0
  94. package/lib/typescript/src/controllers.d.ts.map +1 -0
  95. package/lib/typescript/src/controllers.ios.d.ts +2 -0
  96. package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
  97. package/lib/typescript/src/controllers.native.d.ts +3 -0
  98. package/lib/typescript/src/controllers.native.d.ts.map +1 -0
  99. package/lib/typescript/src/geometry.d.ts +49 -5
  100. package/lib/typescript/src/geometry.d.ts.map +1 -1
  101. package/lib/typescript/src/groupTypes.d.ts +31 -0
  102. package/lib/typescript/src/groupTypes.d.ts.map +1 -0
  103. package/lib/typescript/src/groups.android.d.ts +2 -0
  104. package/lib/typescript/src/groups.android.d.ts.map +1 -0
  105. package/lib/typescript/src/groups.d.ts +2 -0
  106. package/lib/typescript/src/groups.d.ts.map +1 -0
  107. package/lib/typescript/src/groups.ios.d.ts +2 -0
  108. package/lib/typescript/src/groups.ios.d.ts.map +1 -0
  109. package/lib/typescript/src/groups.native.d.ts +3 -0
  110. package/lib/typescript/src/groups.native.d.ts.map +1 -0
  111. package/lib/typescript/src/ids.d.ts +2 -0
  112. package/lib/typescript/src/ids.d.ts.map +1 -0
  113. package/lib/typescript/src/index.d.ts +8 -5
  114. package/lib/typescript/src/index.d.ts.map +1 -1
  115. package/lib/typescript/src/nativeCompletion.d.ts +16 -6
  116. package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
  117. package/lib/typescript/src/presentationCodec.d.ts +10 -0
  118. package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
  119. package/package.json +17 -13
  120. package/src/NativeSmoothClipModule.ts +80 -56
  121. package/src/SmoothClipView.ios.tsx +142 -16
  122. package/src/SmoothClipView.tsx +6 -76
  123. package/src/SmoothClipViewNativeComponent.ts +32 -12
  124. package/src/capabilities.native.ts +19 -0
  125. package/src/capabilities.ts +2 -0
  126. package/src/capabilityTypes.ts +3 -0
  127. package/src/controllerInternals.ts +52 -0
  128. package/src/controllerLifecycle.ts +6 -0
  129. package/src/controllerTypes.ts +87 -0
  130. package/src/controllers.android.ts +1 -0
  131. package/src/controllers.ios.ts +1 -0
  132. package/src/controllers.native.ts +104 -0
  133. package/src/controllers.ts +1 -0
  134. package/src/geometry.ts +293 -45
  135. package/src/groupTypes.ts +53 -0
  136. package/src/groups.android.ts +1 -0
  137. package/src/groups.ios.ts +1 -0
  138. package/src/groups.native.ts +604 -0
  139. package/src/groups.ts +1 -0
  140. package/src/ids.ts +6 -0
  141. package/src/index.ts +32 -12
  142. package/src/nativeCompletion.ts +67 -32
  143. package/src/presentationCodec.ts +134 -0
  144. package/lib/module/SmoothClipView.web.js +0 -54
  145. package/lib/module/SmoothClipView.web.js.map +0 -1
  146. package/lib/module/driverState.js +0 -110
  147. package/lib/module/driverState.js.map +0 -1
  148. package/lib/module/driverTypes.js +0 -4
  149. package/lib/module/driverTypes.js.map +0 -1
  150. package/lib/module/drivers.android.js +0 -6
  151. package/lib/module/drivers.android.js.map +0 -1
  152. package/lib/module/drivers.ios.js +0 -6
  153. package/lib/module/drivers.ios.js.map +0 -1
  154. package/lib/module/drivers.js +0 -284
  155. package/lib/module/drivers.js.map +0 -1
  156. package/lib/module/drivers.native.js +0 -556
  157. package/lib/module/drivers.native.js.map +0 -1
  158. package/lib/module/index.web.js +0 -8
  159. package/lib/module/index.web.js.map +0 -1
  160. package/lib/module/reactRequests.js +0 -56
  161. package/lib/module/reactRequests.js.map +0 -1
  162. package/lib/module/webClipPath.js +0 -25
  163. package/lib/module/webClipPath.js.map +0 -1
  164. package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
  165. package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
  166. package/lib/typescript/src/driverState.d.ts +0 -31
  167. package/lib/typescript/src/driverState.d.ts.map +0 -1
  168. package/lib/typescript/src/driverTypes.d.ts +0 -107
  169. package/lib/typescript/src/driverTypes.d.ts.map +0 -1
  170. package/lib/typescript/src/drivers.android.d.ts +0 -3
  171. package/lib/typescript/src/drivers.android.d.ts.map +0 -1
  172. package/lib/typescript/src/drivers.d.ts +0 -5
  173. package/lib/typescript/src/drivers.d.ts.map +0 -1
  174. package/lib/typescript/src/drivers.ios.d.ts +0 -3
  175. package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
  176. package/lib/typescript/src/drivers.native.d.ts +0 -5
  177. package/lib/typescript/src/drivers.native.d.ts.map +0 -1
  178. package/lib/typescript/src/index.web.d.ts +0 -9
  179. package/lib/typescript/src/index.web.d.ts.map +0 -1
  180. package/lib/typescript/src/reactRequests.d.ts +0 -9
  181. package/lib/typescript/src/reactRequests.d.ts.map +0 -1
  182. package/lib/typescript/src/webClipPath.d.ts +0 -7
  183. package/lib/typescript/src/webClipPath.d.ts.map +0 -1
  184. package/src/SmoothClipView.web.tsx +0 -66
  185. package/src/driverState.ts +0 -158
  186. package/src/driverTypes.ts +0 -140
  187. package/src/drivers.android.ts +0 -8
  188. package/src/drivers.ios.ts +0 -8
  189. package/src/drivers.native.ts +0 -899
  190. package/src/drivers.ts +0 -427
  191. package/src/index.web.ts +0 -23
  192. package/src/reactRequests.ts +0 -76
  193. package/src/webClipPath.ts +0 -30
@@ -7,23 +7,43 @@
7
7
 
8
8
  namespace smoothclip {
9
9
 
10
+ enum class ClipCurve : int32_t {
11
+ Circular = 0,
12
+ Continuous = 1,
13
+ };
14
+
10
15
  struct Geometry {
11
16
  double x;
12
17
  double y;
13
18
  double width;
14
19
  double height;
15
20
  double radius;
21
+ // NaN means "use radius", allowing concise uniform-radius presentations.
22
+ double topLeftRadius = std::numeric_limits<double>::quiet_NaN();
23
+ double topRightRadius = std::numeric_limits<double>::quiet_NaN();
24
+ double bottomRightRadius = std::numeric_limits<double>::quiet_NaN();
25
+ double bottomLeftRadius = std::numeric_limits<double>::quiet_NaN();
26
+ ClipCurve curve = ClipCurve::Circular;
27
+ };
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;
16
39
  };
17
40
 
18
41
  struct Presentation {
19
42
  Geometry clip;
20
43
  double contentTranslateX;
21
44
  double contentTranslateY;
22
- };
23
-
24
- struct Keyframe {
25
- double offset;
26
- Presentation presentation;
45
+ double contentScale = 1.0;
46
+ Shadow shadow{};
27
47
  };
28
48
 
29
49
  struct TimingAnimation {
@@ -42,6 +62,7 @@ struct SpringAnimation {
42
62
  double initialVelocity;
43
63
  bool inheritVelocity;
44
64
  int32_t reduceMotion;
65
+ double energyThreshold = 6e-9;
45
66
  };
46
67
 
47
68
  struct AnimationStart {
@@ -62,19 +83,46 @@ struct CancelResult {
62
83
  Presentation presentation;
63
84
  };
64
85
 
86
+ struct DriverSnapshot {
87
+ uint64_t driverId;
88
+ Presentation presentation;
89
+ bool ready;
90
+ };
91
+
92
+ struct BatchEntry {
93
+ uint64_t driverId;
94
+ Presentation presentation;
95
+ };
96
+
97
+ struct GroupMotionEntry {
98
+ uint64_t driverId;
99
+ bool hasFrom;
100
+ Presentation from;
101
+ Presentation target;
102
+ };
103
+
104
+ enum class GroupCancelBehavior : int32_t {
105
+ Freeze = 0,
106
+ Finish = 1,
107
+ };
108
+
65
109
  using CompletionCallback =
66
- std::function<void(uint64_t, int32_t, bool)>;
110
+ std::function<void(uint64_t, int32_t, int32_t, bool)>;
111
+ using GroupCompletionCallback =
112
+ std::function<
113
+ void(uint64_t, int32_t, int32_t, bool, std::vector<DriverSnapshot>)>;
67
114
 
68
115
  void setCompletionCallback(
69
116
  const void *owner,
70
117
  CompletionCallback callback);
71
118
  void clearCompletionCallback(const void *owner);
119
+ void setGroupCompletionCallback(
120
+ const void *owner,
121
+ GroupCompletionCallback callback);
122
+ void clearGroupCompletionCallback(const void *owner);
72
123
 
73
- // recordVelocity gates the 'inherit' velocity-sample recording for this write.
74
- // Default true (the pre-flag behavior); Android's JS layer passes false for
75
- // setScalars hot writes on drivers without SmoothClipDriverOptions
76
- // .velocityTracking, so the per-frame drag stream skips the clock read and
77
- // 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.
78
126
  void setPresentation(
79
127
  uint64_t driverId,
80
128
  Presentation presentation,
@@ -82,23 +130,39 @@ void setPresentation(
82
130
  bool overridePendingAnimation = false,
83
131
  bool recordVelocity = true);
84
132
  Presentation beginInteraction(uint64_t driverId);
133
+ Presentation snapshotCurrent(uint64_t driverId);
134
+ std::vector<DriverSnapshot> beginGroupInteraction(
135
+ const std::vector<uint64_t> &driverIds);
136
+ std::vector<DriverSnapshot> snapshotGroup(
137
+ const std::vector<uint64_t> &driverIds);
138
+ bool setPresentationBatch(const std::vector<BatchEntry> &entries);
139
+ int32_t animateTimingGroup(
140
+ uint64_t controllerId,
141
+ std::vector<GroupMotionEntry> entries,
142
+ TimingAnimation animation,
143
+ int32_t completionTag = 0,
144
+ double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
145
+ int32_t animateSpringGroup(
146
+ uint64_t controllerId,
147
+ std::vector<GroupMotionEntry> entries,
148
+ SpringAnimation animation,
149
+ int32_t completionTag = 0,
150
+ double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
151
+ std::vector<DriverSnapshot> cancelAnimationGroup(
152
+ int32_t groupId,
153
+ GroupCancelBehavior behavior);
85
154
  int32_t animateTiming(
86
155
  uint64_t driverId,
87
156
  AnimationStart start,
88
157
  Presentation presentation,
89
- TimingAnimation animation);
158
+ TimingAnimation animation,
159
+ int32_t completionTag = 0);
90
160
  int32_t animateSpring(
91
161
  uint64_t driverId,
92
162
  AnimationStart start,
93
163
  Presentation presentation,
94
- SpringAnimation animation);
95
- int32_t animateKeyframes(
96
- uint64_t driverId,
97
- AnimationStart start,
98
- Presentation presentation,
99
- double durationMs,
100
- std::vector<Keyframe> keyframes,
101
- int32_t reduceMotion);
164
+ SpringAnimation animation,
165
+ int32_t completionTag = 0);
102
166
  int32_t rejectAnimation(uint64_t driverId);
103
167
  CancelResult cancelAnimation(
104
168
  uint64_t driverId,
@@ -1,50 +1,159 @@
1
1
  #pragma once
2
2
 
3
+ #include "SmoothClipRegistry.h"
4
+
3
5
  #include <algorithm>
4
6
  #include <cmath>
5
7
 
6
8
  namespace smoothclip {
7
9
 
8
- 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 {
9
17
  double left = 0;
10
18
  double top = 0;
11
19
  double right = 0;
12
20
  double bottom = 0;
13
21
  double radius = 0;
22
+ double topLeftRadius = 0;
23
+ double topRightRadius = 0;
24
+ double bottomRightRadius = 0;
25
+ double bottomLeftRadius = 0;
26
+ ClipCurve curve = ClipCurve::Circular;
14
27
  };
15
28
 
16
- // Shared clip normalization emitting edges (Android's Outline consumes
17
- // edges). Mirrors ios/SmoothClipGeometry.h (SmoothClipNormalizeGeometry) and
18
- // ClipGeometryNormalizer.kt (normalizeClipGeometryPx); a change here must
19
- // land in all three.
20
- inline bool SmoothClipNormalize(
29
+ inline double SmoothClipResolvedRadius(double overrideValue, double radius) {
30
+ return std::isfinite(overrideValue) ? overrideValue : radius;
31
+ }
32
+
33
+ /**
34
+ * Canonicalizes a requested rectangle without consulting host dimensions.
35
+ *
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
+ */
40
+ inline bool SmoothClipCanonicalize(
21
41
  double x,
22
42
  double y,
23
43
  double width,
24
44
  double height,
25
45
  double radius,
26
- double hostWidth,
27
- double hostHeight,
28
- NormalizedClip &out) {
46
+ double topLeftRadius,
47
+ double topRightRadius,
48
+ double bottomRightRadius,
49
+ double bottomLeftRadius,
50
+ ClipCurve curve,
51
+ CanonicalClip &out) {
52
+ const double resolvedTopLeft =
53
+ SmoothClipResolvedRadius(topLeftRadius, radius);
54
+ const double resolvedTopRight =
55
+ SmoothClipResolvedRadius(topRightRadius, radius);
56
+ const double resolvedBottomRight =
57
+ SmoothClipResolvedRadius(bottomRightRadius, radius);
58
+ const double resolvedBottomLeft =
59
+ SmoothClipResolvedRadius(bottomLeftRadius, radius);
29
60
  if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
30
61
  !std::isfinite(height) || !std::isfinite(radius) ||
31
- !std::isfinite(hostWidth) || !std::isfinite(hostHeight)) {
62
+ !std::isfinite(resolvedTopLeft) ||
63
+ !std::isfinite(resolvedTopRight) ||
64
+ !std::isfinite(resolvedBottomRight) ||
65
+ !std::isfinite(resolvedBottomLeft) ||
66
+ (curve != ClipCurve::Circular && curve != ClipCurve::Continuous)) {
32
67
  return false;
33
68
  }
34
69
 
35
- const double boundedHostWidth = std::max(0.0, hostWidth);
36
- const double boundedHostHeight = std::max(0.0, hostHeight);
37
- const double requestedRight = x + std::max(0.0, width);
38
- const double requestedBottom = y + std::max(0.0, height);
39
- out.left = std::min(boundedHostWidth, std::max(0.0, x));
40
- out.top = std::min(boundedHostHeight, std::max(0.0, y));
41
- out.right = std::min(boundedHostWidth, std::max(0.0, requestedRight));
42
- out.bottom = std::min(boundedHostHeight, std::max(0.0, requestedBottom));
43
- const double visibleWidth = std::max(0.0, out.right - out.left);
44
- const double visibleHeight = std::max(0.0, out.bottom - out.top);
45
- out.radius = std::min(
46
- std::max(0.0, radius), std::min(visibleWidth, visibleHeight) / 2.0);
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;
79
+
80
+ double topLeft = std::max(0.0, resolvedTopLeft);
81
+ double topRight = std::max(0.0, resolvedTopRight);
82
+ double bottomRight = std::max(0.0, resolvedBottomRight);
83
+ double bottomLeft = std::max(0.0, resolvedBottomLeft);
84
+ double scale = 1.0;
85
+ const auto includeLimit = [&scale](double available, double requested) {
86
+ if (requested > 0) scale = std::min(scale, available / requested);
87
+ };
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
+
94
+ out.topLeftRadius = topLeft * scale;
95
+ out.topRightRadius = topRight * scale;
96
+ out.bottomRightRadius = bottomRight * scale;
97
+ out.bottomLeftRadius = bottomLeft * scale;
98
+ const bool uniform = out.topLeftRadius == out.topRightRadius &&
99
+ out.topLeftRadius == out.bottomRightRadius &&
100
+ out.topLeftRadius == out.bottomLeftRadius;
101
+ out.radius = uniform ? out.topLeftRadius : 0.0;
102
+ out.curve = curve;
47
103
  return true;
48
104
  }
49
105
 
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(
124
+ double x,
125
+ double y,
126
+ double width,
127
+ double height,
128
+ double radius,
129
+ CanonicalClip &out) {
130
+ return SmoothClipCanonicalize(
131
+ x,
132
+ y,
133
+ width,
134
+ height,
135
+ radius,
136
+ radius,
137
+ radius,
138
+ radius,
139
+ radius,
140
+ ClipCurve::Circular,
141
+ out);
142
+ }
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
+
50
159
  } // namespace smoothclip
@@ -6,17 +6,18 @@
6
6
  namespace smoothclip {
7
7
 
8
8
  // The two most recent interactive samples used by `initialVelocity: 'inherit'`.
9
- // Channels are the seven presentation scalars (x, y, width, height, radius,
10
- // contentTranslateX, contentTranslateY) but the tracker is value-agnostic:
11
- // iOS records normalized (host-clamped) geometry, Android raw DIP — the
12
- // projection only needs both samples and the target in the same space.
9
+ // Channels are the eleven geometry/content presentation scalars. Scale is stored
10
+ // so histories remain coherent but excluded from velocity projection; each of
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.
13
14
  // Shared by ios/SmoothClipView.mm and android/.../SmoothClipRegistry.cpp; a
14
15
  // behavior change here changes both platforms.
15
16
  struct VelocitySampleHistory {
16
17
  bool hasPrevious = false;
17
18
  bool hasLatest = false;
18
- std::array<double, 7> previous{};
19
- std::array<double, 7> latest{};
19
+ std::array<double, 11> previous{};
20
+ std::array<double, 11> latest{};
20
21
  double previousTimeS = 0;
21
22
  double latestTimeS = 0;
22
23
  };
@@ -40,7 +41,7 @@ inline constexpr double kVelocityFullCreditS = 1.0 / 60.0;
40
41
 
41
42
  inline void recordVelocitySample(
42
43
  VelocitySampleHistory &history,
43
- const std::array<double, 7> &channels,
44
+ const std::array<double, 11> &channels,
44
45
  double nowS) {
45
46
  if (history.hasLatest && channels == history.latest) {
46
47
  // Identical re-record (a `from` seed equal to the last interactive
@@ -68,7 +69,7 @@ inline void recordVelocitySample(
68
69
  }
69
70
 
70
71
  // Only explicit interactive writes route through this recorder. Native
71
- // freeze/join/resume/static-finalization paths deliberately do not, so an
72
+ // Native freeze and endpoint-finalization paths deliberately do not, so an
72
73
  // animation cannot manufacture a later inherited velocity. During ordinary
73
74
  // interaction, the staleness decay below is the forgetting mechanism and it
74
75
  // forgets gradually rather than all at once.
@@ -97,7 +98,7 @@ inline double velocityStalenessCredit(double holdS) {
97
98
  // Android seeds each integrator channel with scalar·displacement.
98
99
  inline double inheritedVelocity(
99
100
  const VelocitySampleHistory &history,
100
- const std::array<double, 7> &target,
101
+ const std::array<double, 11> &target,
101
102
  double nowS) {
102
103
  if (!history.hasPrevious || !history.hasLatest) return 0;
103
104
  const double elapsed = history.latestTimeS - history.previousTimeS;
@@ -107,11 +108,14 @@ inline double inheritedVelocity(
107
108
  }
108
109
  double numerator = 0;
109
110
  double denominator = 0;
110
- for (int index = 0; index < 7; index += 1) {
111
+ for (int index = 0; index < 11; index += 1) {
112
+ const double weight = index >= 4 && index <= 7
113
+ ? 0.25
114
+ : (index == 10 ? 0.0 : 1.0);
111
115
  const double sampleDelta = history.latest[index] - history.previous[index];
112
116
  const double destinationDelta = target[index] - history.latest[index];
113
- numerator += sampleDelta * destinationDelta;
114
- denominator += destinationDelta * destinationDelta;
117
+ numerator += weight * sampleDelta * destinationDelta;
118
+ denominator += weight * destinationDelta * destinationDelta;
115
119
  }
116
120
  if (denominator <= 1e-12) return 0;
117
121
  const double result = numerator / elapsed / denominator * credit;
@@ -2,40 +2,207 @@
2
2
 
3
3
  #include <math.h>
4
4
 
5
+ typedef struct {
6
+ CGFloat topLeft;
7
+ CGFloat topRight;
8
+ CGFloat bottomRight;
9
+ CGFloat bottomLeft;
10
+ } SmoothClipCornerRadii;
11
+
12
+ typedef NS_ENUM(NSInteger, SmoothClipCornerCurve) {
13
+ SmoothClipCornerCurveCircular = 0,
14
+ SmoothClipCornerCurveContinuous = 1,
15
+ };
16
+
5
17
  typedef struct {
6
18
  CGRect rect;
19
+ // Uniform-radius fast-path value; zero when canonical radii are unequal.
7
20
  CGFloat radius;
8
- } SmoothNormalizedClipGeometry;
21
+ SmoothClipCornerRadii radii;
22
+ SmoothClipCornerCurve curve;
23
+ } SmoothClipCanonicalGeometry;
9
24
 
10
- /** Mirrors the Android and JavaScript geometry contract without allocating. */
11
- NS_INLINE BOOL SmoothClipNormalizeGeometry(
25
+ NS_INLINE BOOL SmoothClipCornerRadiiEqual(
26
+ SmoothClipCornerRadii first,
27
+ SmoothClipCornerRadii second) {
28
+ return first.topLeft == second.topLeft &&
29
+ first.topRight == second.topRight &&
30
+ first.bottomRight == second.bottomRight &&
31
+ first.bottomLeft == second.bottomLeft;
32
+ }
33
+
34
+ NS_INLINE BOOL SmoothClipCornerRadiiAreUniform(
35
+ SmoothClipCornerRadii radii) {
36
+ return radii.topLeft == radii.topRight &&
37
+ radii.topLeft == radii.bottomRight &&
38
+ radii.topLeft == radii.bottomLeft;
39
+ }
40
+
41
+ NS_INLINE CGFloat SmoothClipSafeOverlapFactor(
42
+ CGFloat extent,
43
+ CGFloat firstRadius,
44
+ CGFloat secondRadius) {
45
+ const CGFloat sum = firstRadius + secondRadius;
46
+ return sum > 0 ? extent / sum : 1;
47
+ }
48
+
49
+ /**
50
+ * Canonicalizes the rectangle and four radii without allocating.
51
+ *
52
+ * The proportional overlap rule matches CSS border radii and the shared
53
+ * Android/C++ contract: all corners use one scale factor, preserving their
54
+ * relative shape while no opposing pair exceeds its edge.
55
+ */
56
+ NS_INLINE BOOL SmoothClipCanonicalizeGeometry(
12
57
  CGFloat x,
13
58
  CGFloat y,
14
59
  CGFloat width,
15
60
  CGFloat height,
16
- CGFloat radius,
17
- CGSize hostSize,
18
- SmoothNormalizedClipGeometry *result) {
61
+ CGFloat topLeftRadius,
62
+ CGFloat topRightRadius,
63
+ CGFloat bottomRightRadius,
64
+ CGFloat bottomLeftRadius,
65
+ NSInteger curveCode,
66
+ SmoothClipCanonicalGeometry *result) {
19
67
  if (!isfinite(x) || !isfinite(y) || !isfinite(width) ||
20
- !isfinite(height) || !isfinite(radius) ||
21
- !isfinite(hostSize.width) || !isfinite(hostSize.height)) {
68
+ !isfinite(height) || !isfinite(topLeftRadius) ||
69
+ !isfinite(topRightRadius) || !isfinite(bottomRightRadius) ||
70
+ !isfinite(bottomLeftRadius) ||
71
+ (curveCode != SmoothClipCornerCurveCircular &&
72
+ curveCode != SmoothClipCornerCurveContinuous)) {
22
73
  return NO;
23
74
  }
24
75
 
25
- const CGFloat hostWidth = MAX(0, hostSize.width);
26
- const CGFloat hostHeight = MAX(0, hostSize.height);
27
76
  const CGFloat requestedWidth = MAX(0, width);
28
77
  const CGFloat requestedHeight = MAX(0, height);
29
- const CGFloat requestedRight = x + requestedWidth;
30
- const CGFloat requestedBottom = y + requestedHeight;
31
- const CGFloat left = MIN(hostWidth, MAX(0, x));
32
- const CGFloat top = MIN(hostHeight, MAX(0, y));
33
- const CGFloat right = MIN(hostWidth, MAX(0, requestedRight));
34
- const CGFloat bottom = MIN(hostHeight, MAX(0, requestedBottom));
35
- const CGFloat visibleWidth = MAX(0, right - left);
36
- const CGFloat visibleHeight = MAX(0, bottom - top);
37
-
38
- result->rect = CGRectMake(left, top, visibleWidth, visibleHeight);
39
- result->radius = MIN(MAX(0, radius), MIN(visibleWidth, visibleHeight) / 2.0);
78
+ if (!isfinite(x + requestedWidth) || !isfinite(y + requestedHeight)) {
79
+ return NO;
80
+ }
81
+
82
+ SmoothClipCornerRadii radii = {
83
+ MAX(0, topLeftRadius),
84
+ MAX(0, topRightRadius),
85
+ MAX(0, bottomRightRadius),
86
+ MAX(0, bottomLeftRadius),
87
+ };
88
+ const CGFloat overlapScale = MIN(
89
+ 1,
90
+ MIN(
91
+ MIN(
92
+ SmoothClipSafeOverlapFactor(
93
+ requestedWidth, radii.topLeft, radii.topRight),
94
+ SmoothClipSafeOverlapFactor(
95
+ requestedWidth, radii.bottomLeft, radii.bottomRight)),
96
+ MIN(
97
+ SmoothClipSafeOverlapFactor(
98
+ requestedHeight, radii.topLeft, radii.bottomLeft),
99
+ SmoothClipSafeOverlapFactor(
100
+ requestedHeight, radii.topRight, radii.bottomRight))));
101
+ radii.topLeft *= overlapScale;
102
+ radii.topRight *= overlapScale;
103
+ radii.bottomRight *= overlapScale;
104
+ radii.bottomLeft *= overlapScale;
105
+
106
+ result->rect = CGRectMake(x, y, requestedWidth, requestedHeight);
107
+ result->radii = radii;
108
+ result->radius = SmoothClipCornerRadiiAreUniform(radii)
109
+ ? radii.topLeft
110
+ : 0;
111
+ result->curve = (SmoothClipCornerCurve)curveCode;
40
112
  return YES;
41
113
  }
114
+
115
+ /** Mirrors the Android and JavaScript geometry contract without allocating. */
116
+ NS_INLINE BOOL SmoothClipCanonicalizeGeometry(
117
+ CGFloat x,
118
+ CGFloat y,
119
+ CGFloat width,
120
+ CGFloat height,
121
+ CGFloat radius,
122
+ SmoothClipCanonicalGeometry *result) {
123
+ return SmoothClipCanonicalizeGeometry(
124
+ x,
125
+ y,
126
+ width,
127
+ height,
128
+ radius,
129
+ radius,
130
+ radius,
131
+ radius,
132
+ SmoothClipCornerCurveCircular,
133
+ result);
134
+ }
135
+
136
+ /**
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.
141
+ */
142
+ NS_INLINE CGPathRef SmoothClipCreateRoundedRectPath(
143
+ CGRect rect,
144
+ SmoothClipCornerRadii radii,
145
+ SmoothClipCornerCurve curve) CF_RETURNS_RETAINED {
146
+ CGMutablePathRef path = CGPathCreateMutable();
147
+ if (CGRectIsEmpty(rect)) return path;
148
+ const CGFloat minX = CGRectGetMinX(rect);
149
+ const CGFloat minY = CGRectGetMinY(rect);
150
+ const CGFloat maxX = CGRectGetMaxX(rect);
151
+ const CGFloat maxY = CGRectGetMaxY(rect);
152
+ // Circular is the exact cubic-circle coefficient. The portable continuous
153
+ // fallback uses kappa=1 so each cubic joins its straight edges with zero
154
+ // curvature, matching the portable Android path. Hit testing derives
155
+ // from this rendered path rather than maintaining a second curve model.
156
+ // Uniform
157
+ // continuous shapes use CALayer.cornerCurve for Apple's platform-native
158
+ // compositor shape and are intentionally not pixel-identical cross-platform.
159
+ const CGFloat controlFactor =
160
+ curve == SmoothClipCornerCurveContinuous
161
+ ? 1.0
162
+ : 0.5522847498307936;
163
+
164
+ CGPathMoveToPoint(path, NULL, minX + radii.topLeft, minY);
165
+ CGPathAddLineToPoint(path, NULL, maxX - radii.topRight, minY);
166
+ CGPathAddCurveToPoint(
167
+ path,
168
+ NULL,
169
+ maxX - radii.topRight + radii.topRight * controlFactor,
170
+ minY,
171
+ maxX,
172
+ minY + radii.topRight - radii.topRight * controlFactor,
173
+ maxX,
174
+ minY + radii.topRight);
175
+ CGPathAddLineToPoint(path, NULL, maxX, maxY - radii.bottomRight);
176
+ CGPathAddCurveToPoint(
177
+ path,
178
+ NULL,
179
+ maxX,
180
+ maxY - radii.bottomRight + radii.bottomRight * controlFactor,
181
+ maxX - radii.bottomRight +
182
+ radii.bottomRight * controlFactor,
183
+ maxY,
184
+ maxX - radii.bottomRight,
185
+ maxY);
186
+ CGPathAddLineToPoint(path, NULL, minX + radii.bottomLeft, maxY);
187
+ CGPathAddCurveToPoint(
188
+ path,
189
+ NULL,
190
+ minX + radii.bottomLeft - radii.bottomLeft * controlFactor,
191
+ maxY,
192
+ minX,
193
+ maxY - radii.bottomLeft + radii.bottomLeft * controlFactor,
194
+ minX,
195
+ maxY - radii.bottomLeft);
196
+ CGPathAddLineToPoint(path, NULL, minX, minY + radii.topLeft);
197
+ CGPathAddCurveToPoint(
198
+ path,
199
+ NULL,
200
+ minX,
201
+ minY + radii.topLeft - radii.topLeft * controlFactor,
202
+ minX + radii.topLeft - radii.topLeft * controlFactor,
203
+ minY,
204
+ minX + radii.topLeft,
205
+ minY);
206
+ CGPathCloseSubpath(path);
207
+ return path;
208
+ }