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
@@ -1,22 +1,23 @@
1
1
  package com.smoothclipview
2
2
 
3
+ import android.graphics.Path
3
4
  import android.view.View
4
5
  import kotlin.math.max
5
6
  import kotlin.math.min
6
- import kotlin.math.roundToInt
7
+
8
+ internal const val CLIP_CURVE_CIRCULAR = 0
9
+ internal const val CLIP_CURVE_CONTINUOUS = 1
7
10
 
8
11
  /**
9
- * Normalizes clip geometry without allocating a result object. The callback is
10
- * inlined into the view's command path so per-frame updates stay allocation-free.
12
+ * Canonicalizes raw clip geometry without allocating a result object. Host
13
+ * bounds deliberately do not participate; SmoothClipView crops at draw time.
11
14
  */
12
- internal inline fun normalizeClipGeometryPx(
15
+ internal inline fun canonicalizeClipGeometryPx(
13
16
  x: Float,
14
17
  y: Float,
15
18
  width: Float,
16
19
  height: Float,
17
20
  radius: Float,
18
- hostWidth: Float,
19
- hostHeight: Float,
20
21
  apply: (
21
22
  left: Float,
22
23
  top: Float,
@@ -26,107 +27,209 @@ internal inline fun normalizeClipGeometryPx(
26
27
  ) -> Unit,
27
28
  ): Boolean {
28
29
  if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
29
- !height.isFinite() || !radius.isFinite() ||
30
- !hostWidth.isFinite() || !hostHeight.isFinite()
30
+ !height.isFinite() || !radius.isFinite()
31
31
  ) {
32
32
  return false
33
33
  }
34
34
 
35
- val boundedHostWidth = hostWidth.coerceAtLeast(0f)
36
- val boundedHostHeight = hostHeight.coerceAtLeast(0f)
37
- val requestedRight = x + width.coerceAtLeast(0f)
38
- val requestedBottom = y + height.coerceAtLeast(0f)
39
- val left = x.coerceIn(0f, boundedHostWidth)
40
- val top = y.coerceIn(0f, boundedHostHeight)
41
- val right = requestedRight.coerceIn(0f, boundedHostWidth)
42
- val bottom = requestedBottom.coerceIn(0f, boundedHostHeight)
43
- val visibleWidth = max(0f, right - left)
44
- val visibleHeight = max(0f, bottom - top)
45
- val normalizedRadius = min(
35
+ val canonicalWidth = max(0f, width)
36
+ val canonicalHeight = max(0f, height)
37
+ val right = x + canonicalWidth
38
+ val bottom = y + canonicalHeight
39
+ if (!right.isFinite() || !bottom.isFinite()) return false
40
+ val canonicalRadius = min(
46
41
  radius.coerceAtLeast(0f),
47
- min(visibleWidth, visibleHeight) / 2f,
42
+ min(canonicalWidth, canonicalHeight) / 2f,
48
43
  )
49
44
 
50
- apply(left, top, right, bottom, normalizedRadius)
45
+ apply(x, y, right, bottom, canonicalRadius)
51
46
  return true
52
47
  }
53
48
 
54
- internal fun clipVisibility(isEmpty: Boolean): Int =
55
- if (isEmpty) View.INVISIBLE else View.VISIBLE
49
+ /**
50
+ * Canonicalization with independent radii. Radii use the CSS overlap rule:
51
+ * one shared factor keeps all four corners proportional when any opposing pair
52
+ * exceeds the requested width or height. This mirrors
53
+ * SmoothClipCanonicalize in the shared C++ delivery path.
54
+ */
55
+ internal inline fun canonicalizeClipGeometryPx(
56
+ x: Float,
57
+ y: Float,
58
+ width: Float,
59
+ height: Float,
60
+ topLeftRadius: Float,
61
+ topRightRadius: Float,
62
+ bottomRightRadius: Float,
63
+ bottomLeftRadius: Float,
64
+ curveCode: Int,
65
+ apply: (
66
+ left: Float,
67
+ top: Float,
68
+ right: Float,
69
+ bottom: Float,
70
+ canonicalTopLeftRadius: Float,
71
+ canonicalTopRightRadius: Float,
72
+ canonicalBottomRightRadius: Float,
73
+ canonicalBottomLeftRadius: Float,
74
+ canonicalCurveCode: Int,
75
+ ) -> Unit,
76
+ ): Boolean {
77
+ if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
78
+ !height.isFinite() || !topLeftRadius.isFinite() ||
79
+ !topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
80
+ !bottomLeftRadius.isFinite() ||
81
+ (curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
82
+ ) {
83
+ return false
84
+ }
85
+
86
+ val canonicalWidth = max(0f, width)
87
+ val canonicalHeight = max(0f, height)
88
+ val right = x + canonicalWidth
89
+ val bottom = y + canonicalHeight
90
+ if (!right.isFinite() || !bottom.isFinite()) return false
91
+ val topLeft = topLeftRadius.coerceAtLeast(0f)
92
+ val topRight = topRightRadius.coerceAtLeast(0f)
93
+ val bottomRight = bottomRightRadius.coerceAtLeast(0f)
94
+ val bottomLeft = bottomLeftRadius.coerceAtLeast(0f)
95
+
96
+ var radiusScale = 1f
97
+ val topWidth = topLeft + topRight
98
+ val bottomWidth = bottomLeft + bottomRight
99
+ val leftHeight = topLeft + bottomLeft
100
+ val rightHeight = topRight + bottomRight
101
+ if (topWidth > 0f) radiusScale = min(radiusScale, canonicalWidth / topWidth)
102
+ if (bottomWidth > 0f) radiusScale = min(radiusScale, canonicalWidth / bottomWidth)
103
+ if (leftHeight > 0f) radiusScale = min(radiusScale, canonicalHeight / leftHeight)
104
+ if (rightHeight > 0f) radiusScale = min(radiusScale, canonicalHeight / rightHeight)
105
+ radiusScale = radiusScale.coerceIn(0f, 1f)
56
106
 
57
- /** Rounded integer origin of an outline edge. */
58
- internal fun outlineOrigin(value: Float): Int = value.roundToInt()
107
+ apply(
108
+ x,
109
+ y,
110
+ right,
111
+ bottom,
112
+ topLeft * radiusScale,
113
+ topRight * radiusScale,
114
+ bottomRight * radiusScale,
115
+ bottomLeft * radiusScale,
116
+ curveCode,
117
+ )
118
+ return true
119
+ }
59
120
 
60
121
  /**
61
- * Rounded far edge, derived from the rounded origin plus the rounded extent.
62
- *
63
- * `Outline.setRoundRect` takes an int rect, and rounding the two edges
64
- * independently makes `round(far) - round(origin)` alternate between the floor
65
- * and the ceil of a *constant* extent as the origin's fraction sweeps: a pure
66
- * translation breathes the emitted size by 1 px, frame after frame, while the
67
- * content inside translates in floats. That oscillation is what reads as
68
- * shimmer on the slow tail of an animation, where the per-frame delta drops
69
- * below a pixel and the rounding becomes the motion.
70
- *
71
- * An int rect cannot hold origin, far edge and extent within half a pixel at
72
- * once (extent = far - origin, so bounding both edges at 0.5 bounds the extent
73
- * at 1.0); the only choice is which error oscillates. Deriving the far edge
74
- * makes the emitted extent a pure function of the true extent — stable, error
75
- * <= 0.5 px — and moves the slack onto the far edge, <= 1.0 px and static. A
76
- * static bias does not read as jank; an alternating one does.
77
- *
78
- * Two consequences, both benign:
79
- * - The far edge can overshoot the host bound by up to 1 px instead of 0.5. The
80
- * view's own bounds already clip, so an over-large outline just means that
81
- * edge is not clipped that frame — which independent rounding already
82
- * produced whenever `round(far)` rounded up.
83
- * - A sub-pixel extent (0 < extent < 0.5) rounds to a degenerate rect, which
84
- * `Outline.setRoundRect` collapses to `setEmpty()`. Independent rounding hit
85
- * that state on some frames and a 1 px rect on others, i.e. it flickered;
86
- * deriving the far edge makes it consistent. The emitted integer rect also
87
- * governs visibility, accessibility and hit testing, so a view never remains
88
- * logically visible while its outline clips every pixel.
122
+ * Emits one fixed-topology rounded rectangle for both curve families.
123
+ * Circular uses the standard quarter-circle cubic coefficient; continuous
124
+ * keeps both control points at the rectangle corner (coefficient 1), yielding
125
+ * zero endpoint curvature and a visibly smoother shoulder. "Continuous" is a
126
+ * library-defined cross-platform curve, not a claim of pixel identity with
127
+ * Apple's private continuous-corner implementation.
89
128
  */
90
- internal fun outlineFarEdge(origin: Float, far: Float): Int =
91
- outlineOrigin(origin) + (far - origin).roundToInt()
129
+ internal fun appendRoundedRectPath(
130
+ path: Path,
131
+ left: Float,
132
+ top: Float,
133
+ right: Float,
134
+ bottom: Float,
135
+ topLeftRadius: Float,
136
+ topRightRadius: Float,
137
+ bottomRightRadius: Float,
138
+ bottomLeftRadius: Float,
139
+ curveCode: Int,
140
+ ) {
141
+ val coefficient = if (curveCode == CLIP_CURVE_CONTINUOUS) {
142
+ 1f
143
+ } else {
144
+ 0.5522848f
145
+ }
92
146
 
93
- /** Exactly mirrors the non-empty requirement of Outline.setRoundRect(Int...). */
94
- internal fun outlineRectIsEmpty(
95
- left: Int,
96
- top: Int,
97
- right: Int,
98
- bottom: Int,
99
- ): Boolean = right <= left || bottom <= top
147
+ path.moveTo(left + topLeftRadius, top)
148
+ path.lineTo(right - topRightRadius, top)
149
+ path.cubicTo(
150
+ right - topRightRadius + coefficient * topRightRadius,
151
+ top,
152
+ right,
153
+ top + topRightRadius - coefficient * topRightRadius,
154
+ right,
155
+ top + topRightRadius,
156
+ )
157
+ path.lineTo(right, bottom - bottomRightRadius)
158
+ path.cubicTo(
159
+ right,
160
+ bottom - bottomRightRadius + coefficient * bottomRightRadius,
161
+ right - bottomRightRadius + coefficient * bottomRightRadius,
162
+ bottom,
163
+ right - bottomRightRadius,
164
+ bottom,
165
+ )
166
+ path.lineTo(left + bottomLeftRadius, bottom)
167
+ path.cubicTo(
168
+ left + bottomLeftRadius - coefficient * bottomLeftRadius,
169
+ bottom,
170
+ left,
171
+ bottom - bottomLeftRadius + coefficient * bottomLeftRadius,
172
+ left,
173
+ bottom - bottomLeftRadius,
174
+ )
175
+ path.lineTo(left, top + topLeftRadius)
176
+ path.cubicTo(
177
+ left,
178
+ top + topLeftRadius - coefficient * topLeftRadius,
179
+ left + topLeftRadius - coefficient * topLeftRadius,
180
+ top,
181
+ left + topLeftRadius,
182
+ top,
183
+ )
184
+ path.close()
185
+ }
100
186
 
101
187
  /**
102
- * Outline dedupe on the rounded integer physical-pixel edges actually emitted
103
- * to the outline provider. Takes the already-derived edges rather than raw
104
- * floats so the caller rounds once per frame and the predicate can never drift
105
- * from what the provider emits; the radius stays a float comparison because it
106
- * is emitted to the outline as a float.
107
- *
108
- * Sub-pixel motion that rounds to the cached integers must NOT invalidate the
109
- * outline — it is carried on the view's translation instead (see
110
- * SmoothClipView.applyClipPlacement), which is both cheaper and smoother than
111
- * restaging an identical rect.
188
+ * Hit-tests the actual rendered path. Path has no float `contains` API, so we
189
+ * flatten that same instance to sub-pixel line segments for ACTION_DOWN. This
190
+ * is deliberately off the render/animation hot path and avoids maintaining a
191
+ * second analytical curve that can drift from clipping geometry.
112
192
  */
113
- internal fun outlineChanged(
114
- left: Int,
115
- top: Int,
116
- right: Int,
117
- bottom: Int,
118
- radius: Float,
119
- cachedLeft: Int,
120
- cachedTop: Int,
121
- cachedRight: Int,
122
- cachedBottom: Int,
123
- cachedRadius: Float,
124
- ): Boolean =
125
- left != cachedLeft ||
126
- top != cachedTop ||
127
- right != cachedRight ||
128
- bottom != cachedBottom ||
129
- radius != cachedRadius
193
+ internal fun containsPathPoint(path: Path, x: Float, y: Float): Boolean {
194
+ if (!x.isFinite() || !y.isFinite() || path.isEmpty) return false
195
+ val points = path.approximate(0.1f)
196
+ if (points.size < 6) return false
197
+
198
+ var inside = false
199
+ var previousX = points[1]
200
+ var previousY = points[2]
201
+ var index = 3
202
+ while (index + 2 < points.size) {
203
+ val currentX = points[index + 1]
204
+ val currentY = points[index + 2]
205
+ val dx = currentX - previousX
206
+ val dy = currentY - previousY
207
+ val segmentLengthSquared = dx * dx + dy * dy
208
+ if (segmentLengthSquared > 0f) {
209
+ val projection = (((x - previousX) * dx + (y - previousY) * dy) /
210
+ segmentLengthSquared).coerceIn(0f, 1f)
211
+ val projectedX = previousX + projection * dx
212
+ val projectedY = previousY + projection * dy
213
+ val distanceX = x - projectedX
214
+ val distanceY = y - projectedY
215
+ if (distanceX * distanceX + distanceY * distanceY <= 0.01f) {
216
+ return true
217
+ }
218
+ }
219
+ if ((currentY > y) != (previousY > y)) {
220
+ val crossingX = (previousX - currentX) * (y - currentY) /
221
+ (previousY - currentY) + currentX
222
+ if (x < crossingX) inside = !inside
223
+ }
224
+ previousX = currentX
225
+ previousY = currentY
226
+ index += 3
227
+ }
228
+ return inside
229
+ }
230
+
231
+ internal fun renderVisibility(rendersIntoHost: Boolean): Int =
232
+ if (rendersIntoHost) View.VISIBLE else View.INVISIBLE
130
233
 
131
234
  internal fun clipAccessibility(
132
235
  isEmpty: Boolean,
@@ -137,32 +240,3 @@ internal fun clipAccessibility(
137
240
  } else {
138
241
  requestedAccessibility
139
242
  }
140
-
141
- internal fun containsRoundedPointPx(
142
- x: Float,
143
- y: Float,
144
- left: Float,
145
- top: Float,
146
- right: Float,
147
- bottom: Float,
148
- radius: Float,
149
- isEmpty: Boolean,
150
- ): Boolean {
151
- if (isEmpty || x < left || x > right || y < top || y > bottom) {
152
- return false
153
- }
154
-
155
- if (radius <= 0f) return true
156
-
157
- val innerLeft = left + radius
158
- val innerRight = right - radius
159
- val innerTop = top + radius
160
- val innerBottom = bottom - radius
161
- if (x in innerLeft..innerRight || y in innerTop..innerBottom) return true
162
-
163
- val centerX = if (x < innerLeft) innerLeft else innerRight
164
- val centerY = if (y < innerTop) innerTop else innerBottom
165
- val dx = x - centerX
166
- val dy = y - centerY
167
- return dx * dx + dy * dy <= radius * radius
168
- }
@@ -26,9 +26,23 @@ internal object SmoothClipBindings {
26
26
  y: Double,
27
27
  width: Double,
28
28
  height: Double,
29
- radius: Double,
29
+ topLeftRadius: Double,
30
+ topRightRadius: Double,
31
+ bottomRightRadius: Double,
32
+ bottomLeftRadius: Double,
33
+ curveCode: Int,
30
34
  contentTranslateX: Double,
31
35
  contentTranslateY: Double,
36
+ contentScale: Double,
37
+ shadowEnabled: Boolean,
38
+ shadowRed: Double,
39
+ shadowGreen: Double,
40
+ shadowBlue: Double,
41
+ shadowAlpha: Double,
42
+ shadowOffsetX: Double,
43
+ shadowOffsetY: Double,
44
+ shadowBlurRadius: Double,
45
+ shadowSpreadDistance: Double,
32
46
  density: Double,
33
47
  widthPx: Double,
34
48
  heightPx: Double,
@@ -36,8 +50,9 @@ internal object SmoothClipBindings {
36
50
  )
37
51
 
38
52
  /**
39
- * Pushes a registered view's density and pixel host size so driver
40
- * deliveries are pre-normalized in C++; redelivers the visible value.
53
+ * Pushes a registered view's density and pixel host size. Density converts
54
+ * raw DIP presentations to pixels; host size controls readiness and the
55
+ * fixed rendering viewport, but never rewrites registry geometry.
41
56
  */
42
57
  external fun nativeSetViewHostGeometry(
43
58
  driverId: Double,
@@ -49,9 +64,12 @@ internal object SmoothClipBindings {
49
64
 
50
65
  external fun nativeUnregisterView(driverId: Double, view: SmoothClipView)
51
66
 
67
+ /** Destroys a controller after [SmoothClipModule] forwards teardown to main. */
68
+ external fun nativeDestroyDriver(driverId: Double)
69
+
52
70
  /**
53
71
  * Pushes both positive and negative attachment/window-visibility state.
54
- * The registry combines it with real host geometry before a latch can run.
72
+ * The registry combines it with real host geometry before a pending run starts.
55
73
  */
56
74
  external fun nativeSetViewLifecycleVisibility(
57
75
  driverId: Double,
@@ -3,6 +3,7 @@ package com.smoothclipview
3
3
  import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.ReactApplicationContext
5
5
  import com.facebook.react.bridge.ReadableArray
6
+ import com.facebook.react.bridge.UiThreadUtil
6
7
  import com.facebook.react.bridge.WritableArray
7
8
  import com.facebook.react.module.annotations.ReactModule
8
9
  import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
@@ -29,92 +30,112 @@ class SmoothClipModule(context: ReactApplicationContext) :
29
30
  SmoothClipBindings.nativeInvalidate()
30
31
  }
31
32
 
33
+ // Static complex paths are supported, but autonomous animation remains a
34
+ // measured capability. Keep it disabled until the physical-device path
35
+ // sampling and frame-time gates are run for this release.
36
+ override fun supportsAutonomousComplexPathAnimation(): Boolean = false
37
+
38
+ override fun beginGroupInteraction(driverIds: ReadableArray): WritableArray =
39
+ Arguments.createArray()
40
+
41
+ override fun snapshotGroup(driverIds: ReadableArray): WritableArray =
42
+ Arguments.createArray()
43
+
44
+ override fun setClipPresentationBatch(entries: ReadableArray): Boolean = false
45
+
46
+ override fun animateTimingGroup(
47
+ controllerId: Double,
48
+ entries: ReadableArray,
49
+ durationMs: Double,
50
+ controlPoint1X: Double,
51
+ controlPoint1Y: Double,
52
+ controlPoint2X: Double,
53
+ controlPoint2Y: Double,
54
+ reduceMotion: Double,
55
+ completionTag: Double,
56
+ startTimestamp: Double,
57
+ ): Double = 0.0
58
+
59
+ override fun animateSpringGroup(
60
+ controllerId: Double,
61
+ entries: ReadableArray,
62
+ mass: Double,
63
+ stiffness: Double,
64
+ damping: Double,
65
+ velocity: Double,
66
+ energyThreshold: Double,
67
+ reduceMotion: Double,
68
+ completionTag: Double,
69
+ startTimestamp: Double,
70
+ ): Double = 0.0
71
+
72
+ override fun cancelAnimationGroup(
73
+ groupId: Double,
74
+ behavior: Double,
75
+ ): WritableArray = Arguments.createArray()
76
+
32
77
  override fun setClipPresentation(
78
+ driverId: Double,
79
+ presentation: ReadableArray,
80
+ takeOwnership: Boolean,
81
+ overridePendingAnimation: Boolean,
82
+ ) = Unit
83
+
84
+ override fun setClipFrameScalars(
33
85
  driverId: Double,
34
86
  x: Double,
35
87
  y: Double,
36
88
  width: Double,
37
89
  height: Double,
38
- radius: Double,
90
+ topLeftRadius: Double,
91
+ topRightRadius: Double,
92
+ bottomRightRadius: Double,
93
+ bottomLeftRadius: Double,
94
+ curveCode: Double,
39
95
  contentTranslateX: Double,
40
96
  contentTranslateY: Double,
41
- takeOwnership: Boolean,
42
- overridePendingAnimation: Boolean,
97
+ contentScale: Double,
98
+ shadowEnabled: Boolean,
99
+ shadowRed: Double,
100
+ shadowGreen: Double,
101
+ shadowBlue: Double,
102
+ shadowAlpha: Double,
103
+ shadowOffsetX: Double,
104
+ shadowOffsetY: Double,
105
+ shadowBlurRadius: Double,
106
+ shadowSpreadDistance: Double,
43
107
  ) = Unit
44
108
 
45
109
  override fun beginInteraction(driverId: Double): WritableArray =
46
110
  Arguments.createArray()
47
111
 
112
+ override fun snapshotCurrent(driverId: Double): WritableArray =
113
+ Arguments.createArray()
114
+
48
115
  override fun animateTiming(
49
116
  driverId: Double,
50
- hasInteractiveStart: Boolean,
51
- startX: Double,
52
- startY: Double,
53
- startWidth: Double,
54
- startHeight: Double,
55
- startRadius: Double,
56
- startContentTranslateX: Double,
57
- startContentTranslateY: Double,
58
- x: Double,
59
- y: Double,
60
- width: Double,
61
- height: Double,
62
- radius: Double,
63
- contentTranslateX: Double,
64
- contentTranslateY: Double,
117
+ start: ReadableArray,
118
+ target: ReadableArray,
65
119
  durationMs: Double,
66
120
  controlPoint1X: Double,
67
121
  controlPoint1Y: Double,
68
122
  controlPoint2X: Double,
69
123
  controlPoint2Y: Double,
70
124
  reduceMotion: Double,
125
+ completionTag: Double,
71
126
  ): Double = 0.0
72
127
 
73
128
  override fun animateSpring(
74
129
  driverId: Double,
75
- hasInteractiveStart: Boolean,
76
- startX: Double,
77
- startY: Double,
78
- startWidth: Double,
79
- startHeight: Double,
80
- startRadius: Double,
81
- startContentTranslateX: Double,
82
- startContentTranslateY: Double,
83
- x: Double,
84
- y: Double,
85
- width: Double,
86
- height: Double,
87
- radius: Double,
88
- contentTranslateX: Double,
89
- contentTranslateY: Double,
130
+ start: ReadableArray,
131
+ target: ReadableArray,
90
132
  mass: Double,
91
133
  stiffness: Double,
92
134
  damping: Double,
93
135
  initialVelocity: Double,
94
136
  inheritVelocity: Boolean,
95
137
  reduceMotion: Double,
96
- ): Double = 0.0
97
-
98
- override fun animateKeyframes(
99
- driverId: Double,
100
- hasInteractiveStart: Boolean,
101
- startX: Double,
102
- startY: Double,
103
- startWidth: Double,
104
- startHeight: Double,
105
- startRadius: Double,
106
- startContentTranslateX: Double,
107
- startContentTranslateY: Double,
108
- x: Double,
109
- y: Double,
110
- width: Double,
111
- height: Double,
112
- radius: Double,
113
- contentTranslateX: Double,
114
- contentTranslateY: Double,
115
- durationMs: Double,
116
- frames: ReadableArray,
117
- reduceMotion: Double,
138
+ completionTag: Double,
118
139
  ): Double = 0.0
119
140
 
120
141
  override fun rejectAnimation(driverId: Double): Double = 0.0
@@ -125,5 +146,9 @@ class SmoothClipModule(context: ReactApplicationContext) :
125
146
  behavior: Double,
126
147
  ): WritableArray = Arguments.createArray()
127
148
 
128
- override fun destroyDriver(driverId: Double) = Unit
149
+ override fun destroyDriver(driverId: Double) {
150
+ UiThreadUtil.runOnUiThread {
151
+ SmoothClipBindings.nativeDestroyDriver(driverId)
152
+ }
153
+ }
129
154
  }