react-native-smooth-clip-view 0.3.0 → 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 (199) hide show
  1. package/README.md +132 -388
  2. package/android/build.gradle +3 -3
  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 +344 -488
  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
@@ -4,23 +4,20 @@ import android.graphics.Path
4
4
  import android.view.View
5
5
  import kotlin.math.max
6
6
  import kotlin.math.min
7
- import kotlin.math.roundToInt
8
7
 
9
8
  internal const val CLIP_CURVE_CIRCULAR = 0
10
9
  internal const val CLIP_CURVE_CONTINUOUS = 1
11
10
 
12
11
  /**
13
- * Normalizes clip geometry without allocating a result object. The callback is
14
- * 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.
15
14
  */
16
- internal inline fun normalizeClipGeometryPx(
15
+ internal inline fun canonicalizeClipGeometryPx(
17
16
  x: Float,
18
17
  y: Float,
19
18
  width: Float,
20
19
  height: Float,
21
20
  radius: Float,
22
- hostWidth: Float,
23
- hostHeight: Float,
24
21
  apply: (
25
22
  left: Float,
26
23
  top: Float,
@@ -30,38 +27,32 @@ internal inline fun normalizeClipGeometryPx(
30
27
  ) -> Unit,
31
28
  ): Boolean {
32
29
  if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
33
- !height.isFinite() || !radius.isFinite() ||
34
- !hostWidth.isFinite() || !hostHeight.isFinite()
30
+ !height.isFinite() || !radius.isFinite()
35
31
  ) {
36
32
  return false
37
33
  }
38
34
 
39
- val boundedHostWidth = hostWidth.coerceAtLeast(0f)
40
- val boundedHostHeight = hostHeight.coerceAtLeast(0f)
41
- val requestedRight = x + width.coerceAtLeast(0f)
42
- val requestedBottom = y + height.coerceAtLeast(0f)
43
- val left = x.coerceIn(0f, boundedHostWidth)
44
- val top = y.coerceIn(0f, boundedHostHeight)
45
- val right = requestedRight.coerceIn(0f, boundedHostWidth)
46
- val bottom = requestedBottom.coerceIn(0f, boundedHostHeight)
47
- val visibleWidth = max(0f, right - left)
48
- val visibleHeight = max(0f, bottom - top)
49
- 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(
50
41
  radius.coerceAtLeast(0f),
51
- min(visibleWidth, visibleHeight) / 2f,
42
+ min(canonicalWidth, canonicalHeight) / 2f,
52
43
  )
53
44
 
54
- apply(left, top, right, bottom, normalizedRadius)
45
+ apply(x, y, right, bottom, canonicalRadius)
55
46
  return true
56
47
  }
57
48
 
58
49
  /**
59
- * V2 normalization with independent radii. Radii use the CSS overlap rule:
50
+ * Canonicalization with independent radii. Radii use the CSS overlap rule:
60
51
  * one shared factor keeps all four corners proportional when any opposing pair
61
- * exceeds the visible width or height. This mirrors SmoothClipNormalizeV2 in
62
- * the shared C++ delivery path.
52
+ * exceeds the requested width or height. This mirrors
53
+ * SmoothClipCanonicalize in the shared C++ delivery path.
63
54
  */
64
- internal inline fun normalizeClipGeometryV2Px(
55
+ internal inline fun canonicalizeClipGeometryPx(
65
56
  x: Float,
66
57
  y: Float,
67
58
  width: Float,
@@ -71,40 +62,32 @@ internal inline fun normalizeClipGeometryV2Px(
71
62
  bottomRightRadius: Float,
72
63
  bottomLeftRadius: Float,
73
64
  curveCode: Int,
74
- hostWidth: Float,
75
- hostHeight: Float,
76
65
  apply: (
77
66
  left: Float,
78
67
  top: Float,
79
68
  right: Float,
80
69
  bottom: Float,
81
- normalizedTopLeftRadius: Float,
82
- normalizedTopRightRadius: Float,
83
- normalizedBottomRightRadius: Float,
84
- normalizedBottomLeftRadius: Float,
85
- normalizedCurveCode: Int,
70
+ canonicalTopLeftRadius: Float,
71
+ canonicalTopRightRadius: Float,
72
+ canonicalBottomRightRadius: Float,
73
+ canonicalBottomLeftRadius: Float,
74
+ canonicalCurveCode: Int,
86
75
  ) -> Unit,
87
76
  ): Boolean {
88
77
  if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
89
78
  !height.isFinite() || !topLeftRadius.isFinite() ||
90
79
  !topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
91
- !bottomLeftRadius.isFinite() || !hostWidth.isFinite() ||
92
- !hostHeight.isFinite() ||
80
+ !bottomLeftRadius.isFinite() ||
93
81
  (curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
94
82
  ) {
95
83
  return false
96
84
  }
97
85
 
98
- val boundedHostWidth = hostWidth.coerceAtLeast(0f)
99
- val boundedHostHeight = hostHeight.coerceAtLeast(0f)
100
- val requestedRight = x + width.coerceAtLeast(0f)
101
- val requestedBottom = y + height.coerceAtLeast(0f)
102
- val left = x.coerceIn(0f, boundedHostWidth)
103
- val top = y.coerceIn(0f, boundedHostHeight)
104
- val right = requestedRight.coerceIn(0f, boundedHostWidth)
105
- val bottom = requestedBottom.coerceIn(0f, boundedHostHeight)
106
- val visibleWidth = max(0f, right - left)
107
- val visibleHeight = max(0f, bottom - top)
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
108
91
  val topLeft = topLeftRadius.coerceAtLeast(0f)
109
92
  val topRight = topRightRadius.coerceAtLeast(0f)
110
93
  val bottomRight = bottomRightRadius.coerceAtLeast(0f)
@@ -115,15 +98,15 @@ internal inline fun normalizeClipGeometryV2Px(
115
98
  val bottomWidth = bottomLeft + bottomRight
116
99
  val leftHeight = topLeft + bottomLeft
117
100
  val rightHeight = topRight + bottomRight
118
- if (topWidth > 0f) radiusScale = min(radiusScale, visibleWidth / topWidth)
119
- if (bottomWidth > 0f) radiusScale = min(radiusScale, visibleWidth / bottomWidth)
120
- if (leftHeight > 0f) radiusScale = min(radiusScale, visibleHeight / leftHeight)
121
- if (rightHeight > 0f) radiusScale = min(radiusScale, visibleHeight / rightHeight)
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)
122
105
  radiusScale = radiusScale.coerceIn(0f, 1f)
123
106
 
124
107
  apply(
125
- left,
126
- top,
108
+ x,
109
+ y,
127
110
  right,
128
111
  bottom,
129
112
  topLeft * radiusScale,
@@ -136,7 +119,7 @@ internal inline fun normalizeClipGeometryV2Px(
136
119
  }
137
120
 
138
121
  /**
139
- * Emits one fixed-topology rounded rectangle for both V2 curve families.
122
+ * Emits one fixed-topology rounded rectangle for both curve families.
140
123
  * Circular uses the standard quarter-circle cubic coefficient; continuous
141
124
  * keeps both control points at the rectangle corner (coefficient 1), yielding
142
125
  * zero endpoint curvature and a visibly smoother shoulder. "Continuous" is a
@@ -245,153 +228,8 @@ internal fun containsPathPoint(path: Path, x: Float, y: Float): Boolean {
245
228
  return inside
246
229
  }
247
230
 
248
- internal fun containsRoundedPointV2Px(
249
- x: Float,
250
- y: Float,
251
- left: Float,
252
- top: Float,
253
- right: Float,
254
- bottom: Float,
255
- topLeftRadius: Float,
256
- topRightRadius: Float,
257
- bottomRightRadius: Float,
258
- bottomLeftRadius: Float,
259
- curveCode: Int,
260
- isEmpty: Boolean,
261
- ): Boolean {
262
- if (isEmpty || x < left || x > right || y < top || y > bottom) return false
263
-
264
- fun containsCorner(inwardX: Float, inwardY: Float): Boolean {
265
- if (curveCode == CLIP_CURVE_CIRCULAR) {
266
- val dx = inwardX - 1f
267
- val dy = inwardY - 1f
268
- return dx * dx + dy * dy <= 1f
269
- }
270
-
271
- // Invert the rendered cubic's inward-x component. This runs only for
272
- // ACTION_DOWN hit testing, never on the animation/render hot path.
273
- var low = 0f
274
- var high = 1f
275
- repeat(12) {
276
- val t = (low + high) / 2f
277
- val inverse = 1f - t
278
- val boundaryInwardX = inverse * inverse * inverse
279
- if (boundaryInwardX > inwardX) {
280
- low = t
281
- } else {
282
- high = t
283
- }
284
- }
285
- val t = (low + high) / 2f
286
- val boundaryInwardY = t * t * t
287
- return inwardY >= boundaryInwardY
288
- }
289
-
290
- if (topLeftRadius > 0f && x < left + topLeftRadius && y < top + topLeftRadius) {
291
- return containsCorner(
292
- (x - left) / topLeftRadius,
293
- (y - top) / topLeftRadius,
294
- )
295
- }
296
- if (topRightRadius > 0f && x > right - topRightRadius && y < top + topRightRadius) {
297
- return containsCorner(
298
- (right - x) / topRightRadius,
299
- (y - top) / topRightRadius,
300
- )
301
- }
302
- if (bottomRightRadius > 0f &&
303
- x > right - bottomRightRadius && y > bottom - bottomRightRadius
304
- ) {
305
- return containsCorner(
306
- (right - x) / bottomRightRadius,
307
- (bottom - y) / bottomRightRadius,
308
- )
309
- }
310
- if (bottomLeftRadius > 0f && x < left + bottomLeftRadius && y > bottom - bottomLeftRadius) {
311
- return containsCorner(
312
- (x - left) / bottomLeftRadius,
313
- (bottom - y) / bottomLeftRadius,
314
- )
315
- }
316
- return true
317
- }
318
-
319
- internal fun clipVisibility(isEmpty: Boolean): Int =
320
- if (isEmpty) View.INVISIBLE else View.VISIBLE
321
-
322
- /** Rounded integer origin of an outline edge. */
323
- internal fun outlineOrigin(value: Float): Int = value.roundToInt()
324
-
325
- /**
326
- * Rounded far edge, derived from the rounded origin plus the rounded extent.
327
- *
328
- * `Outline.setRoundRect` takes an int rect, and rounding the two edges
329
- * independently makes `round(far) - round(origin)` alternate between the floor
330
- * and the ceil of a *constant* extent as the origin's fraction sweeps: a pure
331
- * translation breathes the emitted size by 1 px, frame after frame, while the
332
- * content inside translates in floats. That oscillation is what reads as
333
- * shimmer on the slow tail of an animation, where the per-frame delta drops
334
- * below a pixel and the rounding becomes the motion.
335
- *
336
- * An int rect cannot hold origin, far edge and extent within half a pixel at
337
- * once (extent = far - origin, so bounding both edges at 0.5 bounds the extent
338
- * at 1.0); the only choice is which error oscillates. Deriving the far edge
339
- * makes the emitted extent a pure function of the true extent — stable, error
340
- * <= 0.5 px — and moves the slack onto the far edge, <= 1.0 px and static. A
341
- * static bias does not read as jank; an alternating one does.
342
- *
343
- * Two consequences, both benign:
344
- * - The far edge can overshoot the host bound by up to 1 px instead of 0.5. The
345
- * view's own bounds already clip, so an over-large outline just means that
346
- * edge is not clipped that frame — which independent rounding already
347
- * produced whenever `round(far)` rounded up.
348
- * - A sub-pixel extent (0 < extent < 0.5) rounds to a degenerate rect, which
349
- * `Outline.setRoundRect` collapses to `setEmpty()`. Independent rounding hit
350
- * that state on some frames and a 1 px rect on others, i.e. it flickered;
351
- * deriving the far edge makes it consistent. The emitted integer rect also
352
- * governs visibility, accessibility and hit testing, so a view never remains
353
- * logically visible while its outline clips every pixel.
354
- */
355
- internal fun outlineFarEdge(origin: Float, far: Float): Int =
356
- outlineOrigin(origin) + (far - origin).roundToInt()
357
-
358
- /** Exactly mirrors the non-empty requirement of Outline.setRoundRect(Int...). */
359
- internal fun outlineRectIsEmpty(
360
- left: Int,
361
- top: Int,
362
- right: Int,
363
- bottom: Int,
364
- ): Boolean = right <= left || bottom <= top
365
-
366
- /**
367
- * Outline dedupe on the rounded integer physical-pixel edges actually emitted
368
- * to the outline provider. Takes the already-derived edges rather than raw
369
- * floats so the caller rounds once per frame and the predicate can never drift
370
- * from what the provider emits; the radius stays a float comparison because it
371
- * is emitted to the outline as a float.
372
- *
373
- * Sub-pixel motion that rounds to the cached integers must NOT invalidate the
374
- * outline — it is carried on the view's translation instead (see
375
- * SmoothClipView.applyClipPlacement), which is both cheaper and smoother than
376
- * restaging an identical rect.
377
- */
378
- internal fun outlineChanged(
379
- left: Int,
380
- top: Int,
381
- right: Int,
382
- bottom: Int,
383
- radius: Float,
384
- cachedLeft: Int,
385
- cachedTop: Int,
386
- cachedRight: Int,
387
- cachedBottom: Int,
388
- cachedRadius: Float,
389
- ): Boolean =
390
- left != cachedLeft ||
391
- top != cachedTop ||
392
- right != cachedRight ||
393
- bottom != cachedBottom ||
394
- radius != cachedRadius
231
+ internal fun renderVisibility(rendersIntoHost: Boolean): Int =
232
+ if (rendersIntoHost) View.VISIBLE else View.INVISIBLE
395
233
 
396
234
  internal fun clipAccessibility(
397
235
  isEmpty: Boolean,
@@ -402,32 +240,3 @@ internal fun clipAccessibility(
402
240
  } else {
403
241
  requestedAccessibility
404
242
  }
405
-
406
- internal fun containsRoundedPointPx(
407
- x: Float,
408
- y: Float,
409
- left: Float,
410
- top: Float,
411
- right: Float,
412
- bottom: Float,
413
- radius: Float,
414
- isEmpty: Boolean,
415
- ): Boolean {
416
- if (isEmpty || x < left || x > right || y < top || y > bottom) {
417
- return false
418
- }
419
-
420
- if (radius <= 0f) return true
421
-
422
- val innerLeft = left + radius
423
- val innerRight = right - radius
424
- val innerTop = top + radius
425
- val innerBottom = bottom - radius
426
- if (x in innerLeft..innerRight || y in innerTop..innerBottom) return true
427
-
428
- val centerX = if (x < innerLeft) innerLeft else innerRight
429
- val centerY = if (y < innerTop) innerTop else innerBottom
430
- val dx = x - centerX
431
- val dy = y - centerY
432
- return dx * dx + dy * dy <= radius * radius
433
- }
@@ -20,22 +20,6 @@ internal object SmoothClipBindings {
20
20
  external fun getBindingsInstaller(): BindingsInstallerHolder
21
21
 
22
22
  external fun nativeRegisterView(
23
- driverId: Double,
24
- view: SmoothClipView,
25
- x: Double,
26
- y: Double,
27
- width: Double,
28
- height: Double,
29
- radius: Double,
30
- contentTranslateX: Double,
31
- contentTranslateY: Double,
32
- density: Double,
33
- widthPx: Double,
34
- heightPx: Double,
35
- lifecycleVisible: Boolean,
36
- )
37
-
38
- external fun nativeRegisterViewV2(
39
23
  driverId: Double,
40
24
  view: SmoothClipView,
41
25
  x: Double,
@@ -50,6 +34,15 @@ internal object SmoothClipBindings {
50
34
  contentTranslateX: Double,
51
35
  contentTranslateY: Double,
52
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,
53
46
  density: Double,
54
47
  widthPx: Double,
55
48
  heightPx: Double,
@@ -57,8 +50,9 @@ internal object SmoothClipBindings {
57
50
  )
58
51
 
59
52
  /**
60
- * Pushes a registered view's density and pixel host size so driver
61
- * 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.
62
56
  */
63
57
  external fun nativeSetViewHostGeometry(
64
58
  driverId: Double,
@@ -70,9 +64,12 @@ internal object SmoothClipBindings {
70
64
 
71
65
  external fun nativeUnregisterView(driverId: Double, view: SmoothClipView)
72
66
 
67
+ /** Destroys a controller after [SmoothClipModule] forwards teardown to main. */
68
+ external fun nativeDestroyDriver(driverId: Double)
69
+
73
70
  /**
74
71
  * Pushes both positive and negative attachment/window-visibility state.
75
- * 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.
76
73
  */
77
74
  external fun nativeSetViewLifecycleVisibility(
78
75
  driverId: Double,