react-native-smooth-clip-view 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -21
- package/android/build.gradle +6 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +890 -4
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1056 -44
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +265 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +21 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +283 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +371 -18
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +95 -3
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1028 -22
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +857 -2
- package/ios/SmoothClipTurboModule.h +310 -0
- package/ios/SmoothClipView.mm +714 -183
- package/ios/SmoothClipViewRegistry.h +1 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +22 -6
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -46
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +27 -1
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +36 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +11 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- package/lib/module/driverState.js +0 -28
- package/lib/module/driverState.js.map +1 -1
- package/lib/module/drivers.js +1 -281
- package/lib/module/drivers.js.map +1 -1
- package/lib/module/drivers.native.js +263 -63
- package/lib/module/drivers.native.js.map +1 -1
- package/lib/module/geometry.js +147 -17
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupDriverTypes.js +4 -0
- package/lib/module/groupDriverTypes.js.map +1 -0
- package/lib/module/groupDrivers.android.js +4 -0
- package/lib/module/groupDrivers.android.js.map +1 -0
- package/lib/module/groupDrivers.ios.js +4 -0
- package/lib/module/groupDrivers.ios.js.map +1 -0
- package/lib/module/groupDrivers.js +4 -0
- package/lib/module/groupDrivers.js.map +1 -0
- package/lib/module/groupDrivers.native.js +799 -0
- package/lib/module/groupDrivers.native.js.map +1 -0
- package/lib/module/index.js +3 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/presentationProtocol.js +15 -0
- package/lib/module/presentationProtocol.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +25 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +2 -10
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +133 -2
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.d.ts +3 -0
- package/lib/typescript/src/capabilities.d.ts.map +1 -0
- package/lib/typescript/src/capabilities.native.d.ts +3 -0
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
- package/lib/typescript/src/capabilityTypes.d.ts +10 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- package/lib/typescript/src/driverState.d.ts +1 -6
- package/lib/typescript/src/driverState.d.ts.map +1 -1
- package/lib/typescript/src/driverTypes.d.ts +25 -11
- package/lib/typescript/src/driverTypes.d.ts.map +1 -1
- package/lib/typescript/src/drivers.d.ts +2 -4
- package/lib/typescript/src/drivers.d.ts.map +1 -1
- package/lib/typescript/src/drivers.native.d.ts.map +1 -1
- package/lib/typescript/src/geometry.d.ts +36 -2
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +66 -0
- package/lib/typescript/src/groupDriverTypes.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.android.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.android.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.native.d.ts +4 -0
- package/lib/typescript/src/groupDrivers.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/presentationProtocol.d.ts +4 -0
- package/lib/typescript/src/presentationProtocol.d.ts.map +1 -0
- package/package.json +10 -11
- package/src/NativeSmoothClipModule.ts +275 -0
- package/src/SmoothClipView.ios.tsx +30 -11
- package/src/SmoothClipView.tsx +2 -76
- package/src/SmoothClipViewNativeComponent.ts +27 -1
- package/src/capabilities.native.ts +69 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +17 -0
- package/src/driverState.ts +1 -41
- package/src/driverTypes.ts +42 -11
- package/src/drivers.native.ts +824 -124
- package/src/drivers.ts +2 -423
- package/src/geometry.ts +231 -27
- package/src/groupDriverTypes.ts +117 -0
- package/src/groupDrivers.android.ts +1 -0
- package/src/groupDrivers.ios.ts +1 -0
- package/src/groupDrivers.native.ts +1189 -0
- package/src/groupDrivers.ts +1 -0
- package/src/index.ts +30 -2
- package/src/presentationProtocol.ts +28 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/webClipPath.js +0 -25
- package/lib/module/webClipPath.js.map +0 -1
- package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
- package/lib/typescript/src/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.d.ts.map +0 -1
- package/lib/typescript/src/webClipPath.d.ts +0 -7
- package/lib/typescript/src/webClipPath.d.ts.map +0 -1
- package/src/SmoothClipView.web.tsx +0 -66
- package/src/index.web.ts +0 -23
- package/src/webClipPath.ts +0 -30
|
@@ -2,6 +2,7 @@ package com.smoothclipview
|
|
|
2
2
|
|
|
3
3
|
import android.content.res.Configuration
|
|
4
4
|
import android.graphics.Outline
|
|
5
|
+
import android.graphics.Path
|
|
5
6
|
import android.os.Trace
|
|
6
7
|
import android.view.MotionEvent
|
|
7
8
|
import android.view.View
|
|
@@ -18,13 +19,28 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
18
19
|
private var requestedWidth = 0f
|
|
19
20
|
private var requestedHeight = 0f
|
|
20
21
|
private var requestedRadius = 0f
|
|
22
|
+
private var requestedTopLeftRadius = 0f
|
|
23
|
+
private var requestedTopRightRadius = 0f
|
|
24
|
+
private var requestedBottomRightRadius = 0f
|
|
25
|
+
private var requestedBottomLeftRadius = 0f
|
|
26
|
+
private var requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
21
27
|
private var requestedContentTranslateX = 0f
|
|
22
28
|
private var requestedContentTranslateY = 0f
|
|
29
|
+
private var requestedContentScale = 1f
|
|
30
|
+
private var requestedUsesV2Geometry = false
|
|
23
31
|
private var clipLeft = 0f
|
|
24
32
|
private var clipTop = 0f
|
|
25
33
|
private var clipRight = 0f
|
|
26
34
|
private var clipBottom = 0f
|
|
27
35
|
private var clipRadius = 0f
|
|
36
|
+
private var clipTopLeftRadius = 0f
|
|
37
|
+
private var clipTopRightRadius = 0f
|
|
38
|
+
private var clipBottomRightRadius = 0f
|
|
39
|
+
private var clipBottomLeftRadius = 0f
|
|
40
|
+
private var clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
41
|
+
private val clipPath = Path()
|
|
42
|
+
private var outlineUsesPath = false
|
|
43
|
+
private var outlineUsesFloatRoundRect = false
|
|
28
44
|
// Rounded edges actually emitted to the outline provider. Sub-pixel changes
|
|
29
45
|
// that round to the same integers must not invalidate the outline.
|
|
30
46
|
private var outlineLeft = 0
|
|
@@ -57,13 +73,23 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
57
73
|
return
|
|
58
74
|
}
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
if (outlineUsesPath) {
|
|
77
|
+
outline.setPath(clipPath)
|
|
78
|
+
} else if (outlineUsesFloatRoundRect) {
|
|
79
|
+
// Android's public Outline.setRoundRect overload accepts only
|
|
80
|
+
// integer edges. Path.addRoundRect is the platform float
|
|
81
|
+
// round-rect primitive; Outline.setPath preserves those edges
|
|
82
|
+
// without falling back to V1's integer/residual placement.
|
|
83
|
+
outline.setPath(clipPath)
|
|
84
|
+
} else {
|
|
85
|
+
outline.setRoundRect(
|
|
86
|
+
outlineLeft,
|
|
87
|
+
outlineTop,
|
|
88
|
+
outlineRight,
|
|
89
|
+
outlineBottom,
|
|
90
|
+
clipRadius,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
67
93
|
}
|
|
68
94
|
}
|
|
69
95
|
|
|
@@ -119,8 +145,80 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
119
145
|
requestedWidth = nextWidth
|
|
120
146
|
requestedHeight = nextHeight
|
|
121
147
|
requestedRadius = nextRadius
|
|
148
|
+
requestedTopLeftRadius = nextRadius
|
|
149
|
+
requestedTopRightRadius = nextRadius
|
|
150
|
+
requestedBottomRightRadius = nextRadius
|
|
151
|
+
requestedBottomLeftRadius = nextRadius
|
|
152
|
+
requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
122
153
|
requestedContentTranslateX = nextContentTranslateX
|
|
123
154
|
requestedContentTranslateY = nextContentTranslateY
|
|
155
|
+
requestedContentScale = 1f
|
|
156
|
+
requestedUsesV2Geometry = false
|
|
157
|
+
applyRequestedGeometry()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** V2 DIP fallback used until a registered view has pushed host metrics. */
|
|
161
|
+
@DoNotStrip
|
|
162
|
+
fun setClipPresentationV2Dip(
|
|
163
|
+
x: Double,
|
|
164
|
+
y: Double,
|
|
165
|
+
width: Double,
|
|
166
|
+
height: Double,
|
|
167
|
+
topLeftRadius: Double,
|
|
168
|
+
topRightRadius: Double,
|
|
169
|
+
bottomRightRadius: Double,
|
|
170
|
+
bottomLeftRadius: Double,
|
|
171
|
+
curveCode: Int,
|
|
172
|
+
contentTranslateX: Double,
|
|
173
|
+
contentTranslateY: Double,
|
|
174
|
+
contentScale: Double,
|
|
175
|
+
) {
|
|
176
|
+
if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
|
|
177
|
+
!height.isFinite() || !topLeftRadius.isFinite() ||
|
|
178
|
+
!topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
|
|
179
|
+
!bottomLeftRadius.isFinite() || !contentTranslateX.isFinite() ||
|
|
180
|
+
!contentTranslateY.isFinite() || !contentScale.isFinite() ||
|
|
181
|
+
contentScale <= 0.0 ||
|
|
182
|
+
(curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
|
|
183
|
+
) {
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
val nextX = PixelUtil.toPixelFromDIP(x).toFloat()
|
|
188
|
+
val nextY = PixelUtil.toPixelFromDIP(y).toFloat()
|
|
189
|
+
val nextWidth = PixelUtil.toPixelFromDIP(width).toFloat()
|
|
190
|
+
val nextHeight = PixelUtil.toPixelFromDIP(height).toFloat()
|
|
191
|
+
val nextTopLeftRadius = PixelUtil.toPixelFromDIP(topLeftRadius).toFloat()
|
|
192
|
+
val nextTopRightRadius = PixelUtil.toPixelFromDIP(topRightRadius).toFloat()
|
|
193
|
+
val nextBottomRightRadius = PixelUtil.toPixelFromDIP(bottomRightRadius).toFloat()
|
|
194
|
+
val nextBottomLeftRadius = PixelUtil.toPixelFromDIP(bottomLeftRadius).toFloat()
|
|
195
|
+
val nextContentTranslateX = PixelUtil.toPixelFromDIP(contentTranslateX).toFloat()
|
|
196
|
+
val nextContentTranslateY = PixelUtil.toPixelFromDIP(contentTranslateY).toFloat()
|
|
197
|
+
val nextContentScale = contentScale.toFloat()
|
|
198
|
+
if (!nextX.isFinite() || !nextY.isFinite() || !nextWidth.isFinite() ||
|
|
199
|
+
!nextHeight.isFinite() || !nextTopLeftRadius.isFinite() ||
|
|
200
|
+
!nextTopRightRadius.isFinite() || !nextBottomRightRadius.isFinite() ||
|
|
201
|
+
!nextBottomLeftRadius.isFinite() || !nextContentTranslateX.isFinite() ||
|
|
202
|
+
!nextContentTranslateY.isFinite() || !nextContentScale.isFinite() ||
|
|
203
|
+
nextContentScale <= 0f
|
|
204
|
+
) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
requestedX = nextX
|
|
209
|
+
requestedY = nextY
|
|
210
|
+
requestedWidth = nextWidth
|
|
211
|
+
requestedHeight = nextHeight
|
|
212
|
+
requestedRadius = 0f
|
|
213
|
+
requestedTopLeftRadius = nextTopLeftRadius
|
|
214
|
+
requestedTopRightRadius = nextTopRightRadius
|
|
215
|
+
requestedBottomRightRadius = nextBottomRightRadius
|
|
216
|
+
requestedBottomLeftRadius = nextBottomLeftRadius
|
|
217
|
+
requestedCurveCode = curveCode
|
|
218
|
+
requestedContentTranslateX = nextContentTranslateX
|
|
219
|
+
requestedContentTranslateY = nextContentTranslateY
|
|
220
|
+
requestedContentScale = nextContentScale
|
|
221
|
+
requestedUsesV2Geometry = true
|
|
124
222
|
applyRequestedGeometry()
|
|
125
223
|
}
|
|
126
224
|
|
|
@@ -148,23 +246,103 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
148
246
|
// content's final offset depends on the residual as well.
|
|
149
247
|
requestedContentTranslateX = contentTranslateX
|
|
150
248
|
requestedContentTranslateY = contentTranslateY
|
|
249
|
+
requestedContentScale = 1f
|
|
151
250
|
applyNormalizedClipPx(left, top, right, bottom, radius)
|
|
152
251
|
} finally {
|
|
153
252
|
if (BuildConfig.DEBUG) Trace.endSection()
|
|
154
253
|
}
|
|
155
254
|
}
|
|
156
255
|
|
|
256
|
+
/**
|
|
257
|
+
* V2 driver hot path. Geometry is already intersected with the host and
|
|
258
|
+
* its four radii have already had the CSS overlap factor applied in C++.
|
|
259
|
+
*/
|
|
260
|
+
@DoNotStrip
|
|
261
|
+
fun setClipPresentationV2Px(
|
|
262
|
+
left: Float,
|
|
263
|
+
top: Float,
|
|
264
|
+
right: Float,
|
|
265
|
+
bottom: Float,
|
|
266
|
+
topLeftRadius: Float,
|
|
267
|
+
topRightRadius: Float,
|
|
268
|
+
bottomRightRadius: Float,
|
|
269
|
+
bottomLeftRadius: Float,
|
|
270
|
+
curveCode: Int,
|
|
271
|
+
contentTranslateX: Float,
|
|
272
|
+
contentTranslateY: Float,
|
|
273
|
+
contentScale: Float,
|
|
274
|
+
) {
|
|
275
|
+
if (!left.isFinite() || !top.isFinite() || !right.isFinite() ||
|
|
276
|
+
!bottom.isFinite() || !topLeftRadius.isFinite() ||
|
|
277
|
+
!topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
|
|
278
|
+
!bottomLeftRadius.isFinite() || !contentTranslateX.isFinite() ||
|
|
279
|
+
!contentTranslateY.isFinite() || !contentScale.isFinite() ||
|
|
280
|
+
contentScale <= 0f ||
|
|
281
|
+
(curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
|
|
282
|
+
) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (BuildConfig.DEBUG) Trace.beginSection("SmoothClip.applyV2Px")
|
|
287
|
+
try {
|
|
288
|
+
requestedContentTranslateX = contentTranslateX
|
|
289
|
+
requestedContentTranslateY = contentTranslateY
|
|
290
|
+
requestedContentScale = contentScale
|
|
291
|
+
applyNormalizedClipV2Px(
|
|
292
|
+
left,
|
|
293
|
+
top,
|
|
294
|
+
right,
|
|
295
|
+
bottom,
|
|
296
|
+
topLeftRadius,
|
|
297
|
+
topRightRadius,
|
|
298
|
+
bottomRightRadius,
|
|
299
|
+
bottomLeftRadius,
|
|
300
|
+
curveCode,
|
|
301
|
+
)
|
|
302
|
+
} finally {
|
|
303
|
+
if (BuildConfig.DEBUG) Trace.endSection()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
157
307
|
private fun applyRequestedGeometry() {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
308
|
+
if (requestedUsesV2Geometry) {
|
|
309
|
+
normalizeClipGeometryV2Px(
|
|
310
|
+
requestedX,
|
|
311
|
+
requestedY,
|
|
312
|
+
requestedWidth,
|
|
313
|
+
requestedHeight,
|
|
314
|
+
requestedTopLeftRadius,
|
|
315
|
+
requestedTopRightRadius,
|
|
316
|
+
requestedBottomRightRadius,
|
|
317
|
+
requestedBottomLeftRadius,
|
|
318
|
+
requestedCurveCode,
|
|
319
|
+
width.toFloat(),
|
|
320
|
+
height.toFloat(),
|
|
321
|
+
) { left, top, right, bottom, topLeft, topRight, bottomRight, bottomLeft, curve ->
|
|
322
|
+
applyNormalizedClipV2Px(
|
|
323
|
+
left,
|
|
324
|
+
top,
|
|
325
|
+
right,
|
|
326
|
+
bottom,
|
|
327
|
+
topLeft,
|
|
328
|
+
topRight,
|
|
329
|
+
bottomRight,
|
|
330
|
+
bottomLeft,
|
|
331
|
+
curve,
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
normalizeClipGeometryPx(
|
|
336
|
+
requestedX,
|
|
337
|
+
requestedY,
|
|
338
|
+
requestedWidth,
|
|
339
|
+
requestedHeight,
|
|
340
|
+
requestedRadius,
|
|
341
|
+
width.toFloat(),
|
|
342
|
+
height.toFloat(),
|
|
343
|
+
) { left, top, right, bottom, radius ->
|
|
344
|
+
applyNormalizedClipPx(left, top, right, bottom, radius)
|
|
345
|
+
}
|
|
168
346
|
}
|
|
169
347
|
}
|
|
170
348
|
|
|
@@ -192,7 +370,7 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
192
370
|
nextOutlineRight,
|
|
193
371
|
nextOutlineBottom,
|
|
194
372
|
)
|
|
195
|
-
val outlineGeometryChanged = outlineChanged(
|
|
373
|
+
val outlineGeometryChanged = outlineUsesPath || outlineUsesFloatRoundRect || outlineChanged(
|
|
196
374
|
nextOutlineLeft,
|
|
197
375
|
nextOutlineTop,
|
|
198
376
|
nextOutlineRight,
|
|
@@ -210,6 +388,11 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
210
388
|
clipRight = right
|
|
211
389
|
clipBottom = bottom
|
|
212
390
|
clipRadius = radius
|
|
391
|
+
clipTopLeftRadius = radius
|
|
392
|
+
clipTopRightRadius = radius
|
|
393
|
+
clipBottomRightRadius = radius
|
|
394
|
+
clipBottomLeftRadius = radius
|
|
395
|
+
clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
213
396
|
// Whatever integer rounding threw away, carried on this view's own
|
|
214
397
|
// translation so the clip edge still lands where the driver asked
|
|
215
398
|
// instead of snapping to the pixel grid while the content it clips
|
|
@@ -234,6 +417,9 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
234
417
|
// edge, so the existing edge/radius key cannot skip invalidateOutline.
|
|
235
418
|
if (!outlineGeometryChanged) return
|
|
236
419
|
|
|
420
|
+
outlineUsesPath = false
|
|
421
|
+
outlineUsesFloatRoundRect = false
|
|
422
|
+
clipPath.reset()
|
|
237
423
|
outlineLeft = nextOutlineLeft
|
|
238
424
|
outlineTop = nextOutlineTop
|
|
239
425
|
outlineRight = nextOutlineRight
|
|
@@ -247,6 +433,148 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
247
433
|
invalidateOutline()
|
|
248
434
|
}
|
|
249
435
|
|
|
436
|
+
private fun applyNormalizedClipV2Px(
|
|
437
|
+
left: Float,
|
|
438
|
+
top: Float,
|
|
439
|
+
right: Float,
|
|
440
|
+
bottom: Float,
|
|
441
|
+
topLeftRadius: Float,
|
|
442
|
+
topRightRadius: Float,
|
|
443
|
+
bottomRightRadius: Float,
|
|
444
|
+
bottomLeftRadius: Float,
|
|
445
|
+
curveCode: Int,
|
|
446
|
+
) {
|
|
447
|
+
val radiiAreUniform = topLeftRadius == topRightRadius &&
|
|
448
|
+
topLeftRadius == bottomRightRadius &&
|
|
449
|
+
topLeftRadius == bottomLeftRadius
|
|
450
|
+
val needsPath = curveCode == CLIP_CURVE_CONTINUOUS || !radiiAreUniform
|
|
451
|
+
if (!needsPath) {
|
|
452
|
+
applyNormalizedFloatRoundRectV2Px(
|
|
453
|
+
left,
|
|
454
|
+
top,
|
|
455
|
+
right,
|
|
456
|
+
bottom,
|
|
457
|
+
topLeftRadius,
|
|
458
|
+
)
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
val isEmpty = right <= left || bottom <= top
|
|
463
|
+
val outlineGeometryChanged = !outlineUsesPath ||
|
|
464
|
+
left != clipLeft || top != clipTop || right != clipRight ||
|
|
465
|
+
bottom != clipBottom || topLeftRadius != clipTopLeftRadius ||
|
|
466
|
+
topRightRadius != clipTopRightRadius ||
|
|
467
|
+
bottomRightRadius != clipBottomRightRadius ||
|
|
468
|
+
bottomLeftRadius != clipBottomLeftRadius || curveCode != clipCurveCode
|
|
469
|
+
|
|
470
|
+
clipLeft = left
|
|
471
|
+
clipTop = top
|
|
472
|
+
clipRight = right
|
|
473
|
+
clipBottom = bottom
|
|
474
|
+
clipRadius = if (radiiAreUniform) topLeftRadius else 0f
|
|
475
|
+
clipTopLeftRadius = topLeftRadius
|
|
476
|
+
clipTopRightRadius = topRightRadius
|
|
477
|
+
clipBottomRightRadius = bottomRightRadius
|
|
478
|
+
clipBottomLeftRadius = bottomLeftRadius
|
|
479
|
+
clipCurveCode = curveCode
|
|
480
|
+
// Path outlines retain physical-pixel floats, so unlike the legacy
|
|
481
|
+
// integer round rect they need no residual translation compensation.
|
|
482
|
+
clipResidualX = 0f
|
|
483
|
+
clipResidualY = 0f
|
|
484
|
+
applyClipPlacement()
|
|
485
|
+
|
|
486
|
+
if (isEmpty != clipIsEmpty) {
|
|
487
|
+
if (isEmpty) cancelAcceptedTouchStream()
|
|
488
|
+
clipIsEmpty = isEmpty
|
|
489
|
+
visibility = clipVisibility(isEmpty)
|
|
490
|
+
importantForAccessibility = clipAccessibility(
|
|
491
|
+
isEmpty,
|
|
492
|
+
requestedImportantForAccessibility,
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!outlineGeometryChanged) return
|
|
497
|
+
|
|
498
|
+
outlineUsesPath = true
|
|
499
|
+
outlineUsesFloatRoundRect = false
|
|
500
|
+
clipPath.reset()
|
|
501
|
+
if (!isEmpty) {
|
|
502
|
+
appendRoundedRectPath(
|
|
503
|
+
clipPath,
|
|
504
|
+
left,
|
|
505
|
+
top,
|
|
506
|
+
right,
|
|
507
|
+
bottom,
|
|
508
|
+
topLeftRadius,
|
|
509
|
+
topRightRadius,
|
|
510
|
+
bottomRightRadius,
|
|
511
|
+
bottomLeftRadius,
|
|
512
|
+
curveCode,
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
invalidateOutline()
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Protocol V2 uniform circular geometry stays entirely in float physical
|
|
520
|
+
* pixels. V1 intentionally continues through applyNormalizedClipPx so its
|
|
521
|
+
* integer outline and residual placement remain byte-for-byte compatible.
|
|
522
|
+
*/
|
|
523
|
+
private fun applyNormalizedFloatRoundRectV2Px(
|
|
524
|
+
left: Float,
|
|
525
|
+
top: Float,
|
|
526
|
+
right: Float,
|
|
527
|
+
bottom: Float,
|
|
528
|
+
radius: Float,
|
|
529
|
+
) {
|
|
530
|
+
val isEmpty = right <= left || bottom <= top
|
|
531
|
+
val outlineGeometryChanged = outlineUsesPath ||
|
|
532
|
+
!outlineUsesFloatRoundRect ||
|
|
533
|
+
left != clipLeft || top != clipTop || right != clipRight ||
|
|
534
|
+
bottom != clipBottom || radius != clipRadius
|
|
535
|
+
|
|
536
|
+
clipLeft = left
|
|
537
|
+
clipTop = top
|
|
538
|
+
clipRight = right
|
|
539
|
+
clipBottom = bottom
|
|
540
|
+
clipRadius = radius
|
|
541
|
+
clipTopLeftRadius = radius
|
|
542
|
+
clipTopRightRadius = radius
|
|
543
|
+
clipBottomRightRadius = radius
|
|
544
|
+
clipBottomLeftRadius = radius
|
|
545
|
+
clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
546
|
+
clipResidualX = 0f
|
|
547
|
+
clipResidualY = 0f
|
|
548
|
+
applyClipPlacement()
|
|
549
|
+
|
|
550
|
+
if (isEmpty != clipIsEmpty) {
|
|
551
|
+
if (isEmpty) cancelAcceptedTouchStream()
|
|
552
|
+
clipIsEmpty = isEmpty
|
|
553
|
+
visibility = clipVisibility(isEmpty)
|
|
554
|
+
importantForAccessibility = clipAccessibility(
|
|
555
|
+
isEmpty,
|
|
556
|
+
requestedImportantForAccessibility,
|
|
557
|
+
)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (!outlineGeometryChanged) return
|
|
561
|
+
outlineUsesPath = false
|
|
562
|
+
outlineUsesFloatRoundRect = true
|
|
563
|
+
clipPath.reset()
|
|
564
|
+
if (!isEmpty) {
|
|
565
|
+
clipPath.addRoundRect(
|
|
566
|
+
left,
|
|
567
|
+
top,
|
|
568
|
+
right,
|
|
569
|
+
bottom,
|
|
570
|
+
radius,
|
|
571
|
+
radius,
|
|
572
|
+
Path.Direction.CW,
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
invalidateOutline()
|
|
576
|
+
}
|
|
577
|
+
|
|
250
578
|
/**
|
|
251
579
|
* Single writer for both translations. The view carries the sub-pixel
|
|
252
580
|
* remainder of the clip origin; the content container subtracts it back out
|
|
@@ -259,6 +587,14 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
259
587
|
super.setTranslationY(userTranslationY + clipResidualY)
|
|
260
588
|
contentContainer.translationX = requestedContentTranslateX - clipResidualX
|
|
261
589
|
contentContainer.translationY = requestedContentTranslateY - clipResidualY
|
|
590
|
+
// Scale is intentionally centered and lives on the content only. View
|
|
591
|
+
// translation properties are applied independently of scale, so a
|
|
592
|
+
// caller's tx/ty remains a physical-pixel offset rather than scaling
|
|
593
|
+
// around the origin with the content.
|
|
594
|
+
contentContainer.pivotX = contentContainer.width / 2f
|
|
595
|
+
contentContainer.pivotY = contentContainer.height / 2f
|
|
596
|
+
contentContainer.scaleX = requestedContentScale
|
|
597
|
+
contentContainer.scaleY = requestedContentScale
|
|
262
598
|
}
|
|
263
599
|
|
|
264
600
|
// The clip's sub-pixel placement and the consumer's `transform` prop share
|
|
@@ -286,6 +622,7 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
286
622
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
287
623
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
288
624
|
contentContainer.layout(0, 0, w, h)
|
|
625
|
+
applyClipPlacement()
|
|
289
626
|
if (boundDriverId != 0.0) {
|
|
290
627
|
// Driver deliveries are pre-normalized in C++ against the pushed
|
|
291
628
|
// host metrics; refresh them and let the registry redeliver the
|
|
@@ -363,6 +700,12 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
363
700
|
// back maps the point into the space clipLeft/clipTop are expressed in,
|
|
364
701
|
// so hit testing stays against the geometry the driver delivered — the
|
|
365
702
|
// same test as before the residual existed.
|
|
703
|
+
if (outlineUsesPath) {
|
|
704
|
+
// Reuse the exact cubic path supplied to Outline.setPath: hit
|
|
705
|
+
// testing must follow the rendered aperture, including its
|
|
706
|
+
// portable continuous-curve approximation.
|
|
707
|
+
return !clipIsEmpty && containsPathPoint(clipPath, x, y)
|
|
708
|
+
}
|
|
366
709
|
return containsRoundedPointPx(
|
|
367
710
|
x + clipResidualX,
|
|
368
711
|
y + clipResidualY,
|
|
@@ -465,11 +808,21 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
465
808
|
requestedWidth = 0f
|
|
466
809
|
requestedHeight = 0f
|
|
467
810
|
requestedRadius = 0f
|
|
811
|
+
requestedTopLeftRadius = 0f
|
|
812
|
+
requestedTopRightRadius = 0f
|
|
813
|
+
requestedBottomRightRadius = 0f
|
|
814
|
+
requestedBottomLeftRadius = 0f
|
|
815
|
+
requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
468
816
|
requestedContentTranslateX = 0f
|
|
469
817
|
requestedContentTranslateY = 0f
|
|
818
|
+
requestedContentScale = 1f
|
|
819
|
+
requestedUsesV2Geometry = false
|
|
470
820
|
clipResidualX = 0f
|
|
471
821
|
clipResidualY = 0f
|
|
472
822
|
applyClipPlacement()
|
|
823
|
+
outlineUsesPath = false
|
|
824
|
+
outlineUsesFloatRoundRect = false
|
|
825
|
+
clipPath.reset()
|
|
473
826
|
outlineLeft = 0
|
|
474
827
|
outlineTop = 0
|
|
475
828
|
outlineRight = 0
|
|
@@ -21,8 +21,15 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
21
21
|
var width: Double = 0.0,
|
|
22
22
|
var height: Double = 0.0,
|
|
23
23
|
var radius: Double = 0.0,
|
|
24
|
+
var presentationVersion: Int = 1,
|
|
25
|
+
var topLeftRadius: Double = 0.0,
|
|
26
|
+
var topRightRadius: Double = 0.0,
|
|
27
|
+
var bottomRightRadius: Double = 0.0,
|
|
28
|
+
var bottomLeftRadius: Double = 0.0,
|
|
29
|
+
var curveCode: Int = CLIP_CURVE_CIRCULAR,
|
|
24
30
|
var contentTranslateX: Double = 0.0,
|
|
25
31
|
var contentTranslateY: Double = 0.0,
|
|
32
|
+
var contentScale: Double = 1.0,
|
|
26
33
|
var driverId: Double = 0.0,
|
|
27
34
|
// Whether any prop setter ran in the current update transaction.
|
|
28
35
|
var dirty: Boolean = false,
|
|
@@ -95,6 +102,36 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
95
102
|
pending(view).radius = value
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
@ReactProp(name = "presentationVersion")
|
|
106
|
+
override fun setPresentationVersion(view: SmoothClipView, value: Int) {
|
|
107
|
+
pending(view).presentationVersion = value
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ReactProp(name = "initialClipTopLeftRadius")
|
|
111
|
+
override fun setInitialClipTopLeftRadius(view: SmoothClipView, value: Double) {
|
|
112
|
+
pending(view).topLeftRadius = value
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@ReactProp(name = "initialClipTopRightRadius")
|
|
116
|
+
override fun setInitialClipTopRightRadius(view: SmoothClipView, value: Double) {
|
|
117
|
+
pending(view).topRightRadius = value
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@ReactProp(name = "initialClipBottomRightRadius")
|
|
121
|
+
override fun setInitialClipBottomRightRadius(view: SmoothClipView, value: Double) {
|
|
122
|
+
pending(view).bottomRightRadius = value
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@ReactProp(name = "initialClipBottomLeftRadius")
|
|
126
|
+
override fun setInitialClipBottomLeftRadius(view: SmoothClipView, value: Double) {
|
|
127
|
+
pending(view).bottomLeftRadius = value
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@ReactProp(name = "initialClipCurve")
|
|
131
|
+
override fun setInitialClipCurve(view: SmoothClipView, value: Int) {
|
|
132
|
+
pending(view).curveCode = value
|
|
133
|
+
}
|
|
134
|
+
|
|
98
135
|
@ReactProp(name = "initialContentTranslateX")
|
|
99
136
|
override fun setInitialContentTranslateX(view: SmoothClipView, value: Double) {
|
|
100
137
|
pending(view).contentTranslateX = value
|
|
@@ -105,6 +142,11 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
105
142
|
pending(view).contentTranslateY = value
|
|
106
143
|
}
|
|
107
144
|
|
|
145
|
+
@ReactProp(name = "initialContentScale")
|
|
146
|
+
override fun setInitialContentScale(view: SmoothClipView, value: Double) {
|
|
147
|
+
pending(view).contentScale = value
|
|
148
|
+
}
|
|
149
|
+
|
|
108
150
|
override fun setClipGeometry(
|
|
109
151
|
view: SmoothClipView,
|
|
110
152
|
x: Double,
|
|
@@ -139,6 +181,38 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
139
181
|
)
|
|
140
182
|
}
|
|
141
183
|
|
|
184
|
+
override fun setClipPresentationV2(
|
|
185
|
+
view: SmoothClipView,
|
|
186
|
+
x: Double,
|
|
187
|
+
y: Double,
|
|
188
|
+
width: Double,
|
|
189
|
+
height: Double,
|
|
190
|
+
topLeftRadius: Double,
|
|
191
|
+
topRightRadius: Double,
|
|
192
|
+
bottomRightRadius: Double,
|
|
193
|
+
bottomLeftRadius: Double,
|
|
194
|
+
curveCode: Int,
|
|
195
|
+
contentTranslateX: Double,
|
|
196
|
+
contentTranslateY: Double,
|
|
197
|
+
contentScale: Double,
|
|
198
|
+
) {
|
|
199
|
+
view.commandIsAuthoritative = true
|
|
200
|
+
view.setClipPresentationV2Dip(
|
|
201
|
+
x,
|
|
202
|
+
y,
|
|
203
|
+
width,
|
|
204
|
+
height,
|
|
205
|
+
topLeftRadius,
|
|
206
|
+
topRightRadius,
|
|
207
|
+
bottomRightRadius,
|
|
208
|
+
bottomLeftRadius,
|
|
209
|
+
curveCode,
|
|
210
|
+
contentTranslateX,
|
|
211
|
+
contentTranslateY,
|
|
212
|
+
contentScale,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
142
216
|
override fun onAfterUpdateTransaction(view: SmoothClipView) {
|
|
143
217
|
super.onAfterUpdateTransaction(view)
|
|
144
218
|
val initial = pendingInitialClips[view]
|
|
@@ -154,33 +228,73 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
154
228
|
}
|
|
155
229
|
view.boundDriverId = initial.driverId
|
|
156
230
|
if (initial.driverId != 0.0) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
231
|
+
if (initial.presentationVersion >= 2) {
|
|
232
|
+
SmoothClipBindings.nativeRegisterViewV2(
|
|
233
|
+
initial.driverId,
|
|
234
|
+
view,
|
|
235
|
+
initial.x,
|
|
236
|
+
initial.y,
|
|
237
|
+
initial.width,
|
|
238
|
+
initial.height,
|
|
239
|
+
initial.topLeftRadius,
|
|
240
|
+
initial.topRightRadius,
|
|
241
|
+
initial.bottomRightRadius,
|
|
242
|
+
initial.bottomLeftRadius,
|
|
243
|
+
initial.curveCode,
|
|
244
|
+
initial.contentTranslateX,
|
|
245
|
+
initial.contentTranslateY,
|
|
246
|
+
initial.contentScale,
|
|
247
|
+
view.densityScale(),
|
|
248
|
+
view.width.toDouble(),
|
|
249
|
+
view.height.toDouble(),
|
|
250
|
+
view.isHostLifecycleVisible(),
|
|
251
|
+
)
|
|
252
|
+
} else {
|
|
253
|
+
SmoothClipBindings.nativeRegisterView(
|
|
254
|
+
initial.driverId,
|
|
255
|
+
view,
|
|
256
|
+
initial.x,
|
|
257
|
+
initial.y,
|
|
258
|
+
initial.width,
|
|
259
|
+
initial.height,
|
|
260
|
+
initial.radius,
|
|
261
|
+
initial.contentTranslateX,
|
|
262
|
+
initial.contentTranslateY,
|
|
263
|
+
view.densityScale(),
|
|
264
|
+
view.width.toDouble(),
|
|
265
|
+
view.height.toDouble(),
|
|
266
|
+
view.isHostLifecycleVisible(),
|
|
267
|
+
)
|
|
268
|
+
}
|
|
172
269
|
} else if (!view.commandIsAuthoritative) {
|
|
173
270
|
// Command-driven mode (driverId 0): the initial props are the only
|
|
174
271
|
// source of the first clip, so apply them directly.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
272
|
+
if (initial.presentationVersion >= 2) {
|
|
273
|
+
view.setClipPresentationV2Dip(
|
|
274
|
+
initial.x,
|
|
275
|
+
initial.y,
|
|
276
|
+
initial.width,
|
|
277
|
+
initial.height,
|
|
278
|
+
initial.topLeftRadius,
|
|
279
|
+
initial.topRightRadius,
|
|
280
|
+
initial.bottomRightRadius,
|
|
281
|
+
initial.bottomLeftRadius,
|
|
282
|
+
initial.curveCode,
|
|
283
|
+
initial.contentTranslateX,
|
|
284
|
+
initial.contentTranslateY,
|
|
285
|
+
initial.contentScale,
|
|
286
|
+
)
|
|
287
|
+
} else {
|
|
288
|
+
view.setClipPresentationDip(
|
|
289
|
+
initial.x,
|
|
290
|
+
initial.y,
|
|
291
|
+
initial.width,
|
|
292
|
+
initial.height,
|
|
293
|
+
initial.radius,
|
|
294
|
+
initial.contentTranslateX,
|
|
295
|
+
initial.contentTranslateY,
|
|
296
|
+
)
|
|
297
|
+
}
|
|
184
298
|
} else {
|
|
185
299
|
view.reapplyClipPresentation()
|
|
186
300
|
}
|