react-native-smooth-clip-view 0.2.8 → 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 +86 -21
- package/android/CMakeLists.txt +7 -0
- package/android/build.gradle +14 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +896 -6
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1067 -46
- package/android/src/main/cpp/SmoothClipTrace.h +25 -0
- 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 +385 -24
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +102 -4
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1033 -25
- 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 +340 -75
- 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 +39 -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 +56 -11
- package/src/drivers.native.ts +921 -151
- 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,8 @@ package com.smoothclipview
|
|
|
2
2
|
|
|
3
3
|
import android.content.res.Configuration
|
|
4
4
|
import android.graphics.Outline
|
|
5
|
+
import android.graphics.Path
|
|
6
|
+
import android.os.Trace
|
|
5
7
|
import android.view.MotionEvent
|
|
6
8
|
import android.view.View
|
|
7
9
|
import android.view.ViewOutlineProvider
|
|
@@ -17,13 +19,28 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
17
19
|
private var requestedWidth = 0f
|
|
18
20
|
private var requestedHeight = 0f
|
|
19
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
|
|
20
27
|
private var requestedContentTranslateX = 0f
|
|
21
28
|
private var requestedContentTranslateY = 0f
|
|
29
|
+
private var requestedContentScale = 1f
|
|
30
|
+
private var requestedUsesV2Geometry = false
|
|
22
31
|
private var clipLeft = 0f
|
|
23
32
|
private var clipTop = 0f
|
|
24
33
|
private var clipRight = 0f
|
|
25
34
|
private var clipBottom = 0f
|
|
26
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
|
|
27
44
|
// Rounded edges actually emitted to the outline provider. Sub-pixel changes
|
|
28
45
|
// that round to the same integers must not invalidate the outline.
|
|
29
46
|
private var outlineLeft = 0
|
|
@@ -56,13 +73,23 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
56
73
|
return
|
|
57
74
|
}
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
|
66
93
|
}
|
|
67
94
|
}
|
|
68
95
|
|
|
@@ -118,8 +145,80 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
118
145
|
requestedWidth = nextWidth
|
|
119
146
|
requestedHeight = nextHeight
|
|
120
147
|
requestedRadius = nextRadius
|
|
148
|
+
requestedTopLeftRadius = nextRadius
|
|
149
|
+
requestedTopRightRadius = nextRadius
|
|
150
|
+
requestedBottomRightRadius = nextRadius
|
|
151
|
+
requestedBottomLeftRadius = nextRadius
|
|
152
|
+
requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
121
153
|
requestedContentTranslateX = nextContentTranslateX
|
|
122
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
|
|
123
222
|
applyRequestedGeometry()
|
|
124
223
|
}
|
|
125
224
|
|
|
@@ -138,25 +237,112 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
138
237
|
contentTranslateX: Float,
|
|
139
238
|
contentTranslateY: Float,
|
|
140
239
|
) {
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
240
|
+
// Debug-only Perfetto slice for the Kotlin share of the C++
|
|
241
|
+
// SmoothClip.setPresentation section (see SmoothClipTrace.h).
|
|
242
|
+
if (BuildConfig.DEBUG) Trace.beginSection("SmoothClip.applyPx")
|
|
243
|
+
try {
|
|
244
|
+
// Stored only; applyNormalizedClipPx computes the clip residual and
|
|
245
|
+
// applyClipPlacement writes both translations together, because the
|
|
246
|
+
// content's final offset depends on the residual as well.
|
|
247
|
+
requestedContentTranslateX = contentTranslateX
|
|
248
|
+
requestedContentTranslateY = contentTranslateY
|
|
249
|
+
requestedContentScale = 1f
|
|
250
|
+
applyNormalizedClipPx(left, top, right, bottom, radius)
|
|
251
|
+
} finally {
|
|
252
|
+
if (BuildConfig.DEBUG) Trace.endSection()
|
|
253
|
+
}
|
|
254
|
+
}
|
|
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
|
+
}
|
|
147
305
|
}
|
|
148
306
|
|
|
149
307
|
private fun applyRequestedGeometry() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
}
|
|
160
346
|
}
|
|
161
347
|
}
|
|
162
348
|
|
|
@@ -184,7 +370,7 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
184
370
|
nextOutlineRight,
|
|
185
371
|
nextOutlineBottom,
|
|
186
372
|
)
|
|
187
|
-
val outlineGeometryChanged = outlineChanged(
|
|
373
|
+
val outlineGeometryChanged = outlineUsesPath || outlineUsesFloatRoundRect || outlineChanged(
|
|
188
374
|
nextOutlineLeft,
|
|
189
375
|
nextOutlineTop,
|
|
190
376
|
nextOutlineRight,
|
|
@@ -202,6 +388,11 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
202
388
|
clipRight = right
|
|
203
389
|
clipBottom = bottom
|
|
204
390
|
clipRadius = radius
|
|
391
|
+
clipTopLeftRadius = radius
|
|
392
|
+
clipTopRightRadius = radius
|
|
393
|
+
clipBottomRightRadius = radius
|
|
394
|
+
clipBottomLeftRadius = radius
|
|
395
|
+
clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
205
396
|
// Whatever integer rounding threw away, carried on this view's own
|
|
206
397
|
// translation so the clip edge still lands where the driver asked
|
|
207
398
|
// instead of snapping to the pixel grid while the content it clips
|
|
@@ -226,6 +417,9 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
226
417
|
// edge, so the existing edge/radius key cannot skip invalidateOutline.
|
|
227
418
|
if (!outlineGeometryChanged) return
|
|
228
419
|
|
|
420
|
+
outlineUsesPath = false
|
|
421
|
+
outlineUsesFloatRoundRect = false
|
|
422
|
+
clipPath.reset()
|
|
229
423
|
outlineLeft = nextOutlineLeft
|
|
230
424
|
outlineTop = nextOutlineTop
|
|
231
425
|
outlineRight = nextOutlineRight
|
|
@@ -239,6 +433,148 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
239
433
|
invalidateOutline()
|
|
240
434
|
}
|
|
241
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
|
+
|
|
242
578
|
/**
|
|
243
579
|
* Single writer for both translations. The view carries the sub-pixel
|
|
244
580
|
* remainder of the clip origin; the content container subtracts it back out
|
|
@@ -251,6 +587,14 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
251
587
|
super.setTranslationY(userTranslationY + clipResidualY)
|
|
252
588
|
contentContainer.translationX = requestedContentTranslateX - clipResidualX
|
|
253
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
|
|
254
598
|
}
|
|
255
599
|
|
|
256
600
|
// The clip's sub-pixel placement and the consumer's `transform` prop share
|
|
@@ -278,6 +622,7 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
278
622
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
279
623
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
280
624
|
contentContainer.layout(0, 0, w, h)
|
|
625
|
+
applyClipPlacement()
|
|
281
626
|
if (boundDriverId != 0.0) {
|
|
282
627
|
// Driver deliveries are pre-normalized in C++ against the pushed
|
|
283
628
|
// host metrics; refresh them and let the registry redeliver the
|
|
@@ -355,6 +700,12 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
355
700
|
// back maps the point into the space clipLeft/clipTop are expressed in,
|
|
356
701
|
// so hit testing stays against the geometry the driver delivered — the
|
|
357
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
|
+
}
|
|
358
709
|
return containsRoundedPointPx(
|
|
359
710
|
x + clipResidualX,
|
|
360
711
|
y + clipResidualY,
|
|
@@ -457,11 +808,21 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
457
808
|
requestedWidth = 0f
|
|
458
809
|
requestedHeight = 0f
|
|
459
810
|
requestedRadius = 0f
|
|
811
|
+
requestedTopLeftRadius = 0f
|
|
812
|
+
requestedTopRightRadius = 0f
|
|
813
|
+
requestedBottomRightRadius = 0f
|
|
814
|
+
requestedBottomLeftRadius = 0f
|
|
815
|
+
requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
460
816
|
requestedContentTranslateX = 0f
|
|
461
817
|
requestedContentTranslateY = 0f
|
|
818
|
+
requestedContentScale = 1f
|
|
819
|
+
requestedUsesV2Geometry = false
|
|
462
820
|
clipResidualX = 0f
|
|
463
821
|
clipResidualY = 0f
|
|
464
822
|
applyClipPlacement()
|
|
823
|
+
outlineUsesPath = false
|
|
824
|
+
outlineUsesFloatRoundRect = false
|
|
825
|
+
clipPath.reset()
|
|
465
826
|
outlineLeft = 0
|
|
466
827
|
outlineTop = 0
|
|
467
828
|
outlineRight = 0
|