react-native-smooth-clip-view 0.1.0 → 0.2.1
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 +171 -43
- package/SmoothClipView.podspec +8 -2
- package/android/CMakeLists.txt +39 -0
- package/android/build.gradle +69 -2
- package/android/proguard-rules.pro +6 -0
- package/android/src/main/cpp/SmoothClipAndroid.h +59 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +428 -0
- package/android/src/main/cpp/SmoothClipRegistry.cpp +945 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +25 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +55 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +128 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +164 -30
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +107 -10
- package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +18 -2
- package/cpp/SmoothClipRegistry.h +93 -0
- package/cpp/SmoothClipSharedGeometry.h +50 -0
- package/ios/SmoothClipModuleProvider.h +9 -0
- package/ios/SmoothClipModuleProvider.mm +16 -0
- package/ios/SmoothClipRegistry.mm +754 -0
- package/ios/SmoothClipTurboModule.cpp +290 -0
- package/ios/SmoothClipTurboModule.h +132 -0
- package/ios/SmoothClipView.mm +976 -85
- package/ios/SmoothClipViewRegistry.h +27 -0
- package/lib/module/NativeSmoothClipModule.js +5 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -0
- package/lib/module/SmoothClipView.android.js +4 -0
- package/lib/module/SmoothClipView.android.js.map +1 -0
- package/lib/module/SmoothClipView.ios.js +34 -0
- package/lib/module/SmoothClipView.ios.js.map +1 -0
- package/lib/module/SmoothClipView.js +26 -12
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipView.web.js +28 -12
- package/lib/module/SmoothClipView.web.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +14 -1
- package/lib/module/driverState.js +102 -0
- package/lib/module/driverState.js.map +1 -0
- package/lib/module/driverTypes.js +4 -0
- package/lib/module/driverTypes.js.map +1 -0
- package/lib/module/drivers.android.js +4 -0
- package/lib/module/drivers.android.js.map +1 -0
- package/lib/module/drivers.ios.js +380 -0
- package/lib/module/drivers.ios.js.map +1 -0
- package/lib/module/drivers.js +276 -0
- package/lib/module/drivers.js.map +1 -0
- package/lib/module/geometry.js +19 -0
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/nativeCompletion.js +21 -0
- package/lib/module/nativeCompletion.js.map +1 -0
- package/lib/module/reactRequests.js +50 -0
- package/lib/module/reactRequests.js.map +1 -0
- package/lib/module/smoothClipNative.android.js +15 -0
- package/lib/module/smoothClipNative.android.js.map +1 -0
- package/lib/module/smoothClipNative.js +8 -0
- package/lib/module/smoothClipNative.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +19 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts +3 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.d.ts +5 -6
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +9 -0
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.web.d.ts +5 -7
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/driverState.d.ts +31 -0
- package/lib/typescript/src/driverState.d.ts.map +1 -0
- package/lib/typescript/src/driverTypes.d.ts +61 -0
- package/lib/typescript/src/driverTypes.d.ts.map +1 -0
- package/lib/typescript/src/drivers.android.d.ts +3 -0
- package/lib/typescript/src/drivers.android.d.ts.map +1 -0
- package/lib/typescript/src/drivers.d.ts +5 -0
- package/lib/typescript/src/drivers.d.ts.map +1 -0
- package/lib/typescript/src/drivers.ios.d.ts +5 -0
- package/lib/typescript/src/drivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +8 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +4 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +7 -0
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -0
- package/lib/typescript/src/reactRequests.d.ts +7 -0
- package/lib/typescript/src/reactRequests.d.ts.map +1 -0
- package/lib/typescript/src/smoothClipNative.android.d.ts +4 -0
- package/lib/typescript/src/smoothClipNative.android.d.ts.map +1 -0
- package/lib/typescript/src/smoothClipNative.d.ts +3 -0
- package/lib/typescript/src/smoothClipNative.d.ts.map +1 -0
- package/package.json +12 -7
- package/src/NativeSmoothClipModule.ts +104 -0
- package/src/SmoothClipView.android.tsx +2 -0
- package/src/SmoothClipView.ios.tsx +36 -0
- package/src/SmoothClipView.tsx +31 -27
- package/src/SmoothClipView.web.tsx +26 -22
- package/src/SmoothClipViewNativeComponent.ts +14 -1
- package/src/driverState.ts +150 -0
- package/src/driverTypes.ts +91 -0
- package/src/drivers.android.ts +6 -0
- package/src/drivers.ios.ts +665 -0
- package/src/drivers.ts +416 -0
- package/src/geometry.ts +39 -0
- package/src/index.ts +19 -1
- package/src/index.web.ts +19 -1
- package/src/nativeCompletion.ts +37 -0
- package/src/reactRequests.ts +64 -0
- package/src/smoothClipNative.android.ts +20 -0
- package/src/smoothClipNative.ts +5 -0
|
@@ -3,6 +3,7 @@ package com.smoothclipview
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import kotlin.math.max
|
|
5
5
|
import kotlin.math.min
|
|
6
|
+
import kotlin.math.roundToInt
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Normalizes clip geometry without allocating a result object. The callback is
|
|
@@ -53,6 +54,30 @@ internal inline fun normalizeClipGeometryPx(
|
|
|
53
54
|
internal fun clipVisibility(isEmpty: Boolean): Int =
|
|
54
55
|
if (isEmpty) View.INVISIBLE else View.VISIBLE
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Outline dedupe on the rounded integer physical-pixel edges actually emitted
|
|
59
|
+
* to the outline provider. Sub-pixel motion that rounds to the cached integers
|
|
60
|
+
* must not invalidate the outline; the radius stays a float comparison because
|
|
61
|
+
* it is emitted to the outline as a float.
|
|
62
|
+
*/
|
|
63
|
+
internal fun outlineChanged(
|
|
64
|
+
left: Float,
|
|
65
|
+
top: Float,
|
|
66
|
+
right: Float,
|
|
67
|
+
bottom: Float,
|
|
68
|
+
radius: Float,
|
|
69
|
+
cachedLeft: Int,
|
|
70
|
+
cachedTop: Int,
|
|
71
|
+
cachedRight: Int,
|
|
72
|
+
cachedBottom: Int,
|
|
73
|
+
cachedRadius: Float,
|
|
74
|
+
): Boolean =
|
|
75
|
+
left.roundToInt() != cachedLeft ||
|
|
76
|
+
top.roundToInt() != cachedTop ||
|
|
77
|
+
right.roundToInt() != cachedRight ||
|
|
78
|
+
bottom.roundToInt() != cachedBottom ||
|
|
79
|
+
radius != cachedRadius
|
|
80
|
+
|
|
56
81
|
internal fun clipAccessibility(
|
|
57
82
|
isEmpty: Boolean,
|
|
58
83
|
requestedAccessibility: Int,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
package com.smoothclipview
|
|
2
|
+
|
|
3
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
4
|
+
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Bridge to the shared C++ SmoothClip registry. All driver state, animation and
|
|
8
|
+
* geometry live in native C++ (see android/src/main/cpp); this object only loads
|
|
9
|
+
* the library, exposes the worklet-callable JSI bindings installer, and forwards
|
|
10
|
+
* Fabric view lifecycle to the registry.
|
|
11
|
+
*/
|
|
12
|
+
@DoNotStrip
|
|
13
|
+
internal object SmoothClipBindings {
|
|
14
|
+
init {
|
|
15
|
+
System.loadLibrary("smoothclipview")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Installs `global.__SmoothClipView` and wires completion delivery. */
|
|
19
|
+
external fun getBindingsInstaller(): BindingsInstallerHolder
|
|
20
|
+
|
|
21
|
+
external fun nativeRegisterView(
|
|
22
|
+
driverId: Double,
|
|
23
|
+
view: SmoothClipView,
|
|
24
|
+
x: Double,
|
|
25
|
+
y: Double,
|
|
26
|
+
width: Double,
|
|
27
|
+
height: Double,
|
|
28
|
+
radius: Double,
|
|
29
|
+
contentTranslateX: Double,
|
|
30
|
+
contentTranslateY: Double,
|
|
31
|
+
density: Double,
|
|
32
|
+
widthPx: Double,
|
|
33
|
+
heightPx: Double,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pushes a registered view's density and pixel host size so driver
|
|
38
|
+
* deliveries are pre-normalized in C++; redelivers the visible value.
|
|
39
|
+
*/
|
|
40
|
+
external fun nativeSetViewHostGeometry(
|
|
41
|
+
driverId: Double,
|
|
42
|
+
view: SmoothClipView,
|
|
43
|
+
density: Double,
|
|
44
|
+
widthPx: Double,
|
|
45
|
+
heightPx: Double,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
external fun nativeUnregisterView(driverId: Double, view: SmoothClipView)
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Releases runtime-bound listener state during host teardown; must run
|
|
52
|
+
* while the JS runtime is still alive (module invalidate).
|
|
53
|
+
*/
|
|
54
|
+
external fun nativeInvalidate()
|
|
55
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package com.smoothclipview
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReadableArray
|
|
6
|
+
import com.facebook.react.bridge.WritableArray
|
|
7
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
8
|
+
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
|
|
9
|
+
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The Android driver runs entirely through worklet-callable C++ JSI bindings
|
|
13
|
+
* (installed via [getBindingsInstaller]); JS resolves `global.__SmoothClipView`
|
|
14
|
+
* instead of these TurboModule methods. The spec methods below are required by
|
|
15
|
+
* codegen but are never invoked on Android.
|
|
16
|
+
*/
|
|
17
|
+
@ReactModule(name = NativeSmoothClipModuleSpec.NAME)
|
|
18
|
+
class SmoothClipModule(context: ReactApplicationContext) :
|
|
19
|
+
NativeSmoothClipModuleSpec(context), TurboModuleWithJSIBindings {
|
|
20
|
+
|
|
21
|
+
override fun getBindingsInstaller(): BindingsInstallerHolder =
|
|
22
|
+
SmoothClipBindings.getBindingsInstaller()
|
|
23
|
+
|
|
24
|
+
override fun invalidate() {
|
|
25
|
+
super.invalidate()
|
|
26
|
+
// Completions must stop and runtime-bound listener functions must be
|
|
27
|
+
// released before the JS runtime is torn down; otherwise a later host
|
|
28
|
+
// would deliver completions into a destroyed runtime.
|
|
29
|
+
SmoothClipBindings.nativeInvalidate()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun setClipPresentation(
|
|
33
|
+
driverId: Double,
|
|
34
|
+
x: Double,
|
|
35
|
+
y: Double,
|
|
36
|
+
width: Double,
|
|
37
|
+
height: Double,
|
|
38
|
+
radius: Double,
|
|
39
|
+
contentTranslateX: Double,
|
|
40
|
+
contentTranslateY: Double,
|
|
41
|
+
takeOwnership: Boolean,
|
|
42
|
+
) = Unit
|
|
43
|
+
|
|
44
|
+
override fun beginInteraction(driverId: Double): WritableArray =
|
|
45
|
+
Arguments.createArray()
|
|
46
|
+
|
|
47
|
+
override fun animateTiming(
|
|
48
|
+
driverId: Double,
|
|
49
|
+
hasInteractiveStart: Boolean,
|
|
50
|
+
startX: Double,
|
|
51
|
+
startY: Double,
|
|
52
|
+
startWidth: Double,
|
|
53
|
+
startHeight: Double,
|
|
54
|
+
startRadius: Double,
|
|
55
|
+
startContentTranslateX: Double,
|
|
56
|
+
startContentTranslateY: Double,
|
|
57
|
+
x: Double,
|
|
58
|
+
y: Double,
|
|
59
|
+
width: Double,
|
|
60
|
+
height: Double,
|
|
61
|
+
radius: Double,
|
|
62
|
+
contentTranslateX: Double,
|
|
63
|
+
contentTranslateY: Double,
|
|
64
|
+
durationMs: Double,
|
|
65
|
+
controlPoint1X: Double,
|
|
66
|
+
controlPoint1Y: Double,
|
|
67
|
+
controlPoint2X: Double,
|
|
68
|
+
controlPoint2Y: Double,
|
|
69
|
+
reduceMotion: Double,
|
|
70
|
+
): Double = 0.0
|
|
71
|
+
|
|
72
|
+
override fun animateSpring(
|
|
73
|
+
driverId: Double,
|
|
74
|
+
hasInteractiveStart: Boolean,
|
|
75
|
+
startX: Double,
|
|
76
|
+
startY: Double,
|
|
77
|
+
startWidth: Double,
|
|
78
|
+
startHeight: Double,
|
|
79
|
+
startRadius: Double,
|
|
80
|
+
startContentTranslateX: Double,
|
|
81
|
+
startContentTranslateY: Double,
|
|
82
|
+
x: Double,
|
|
83
|
+
y: Double,
|
|
84
|
+
width: Double,
|
|
85
|
+
height: Double,
|
|
86
|
+
radius: Double,
|
|
87
|
+
contentTranslateX: Double,
|
|
88
|
+
contentTranslateY: Double,
|
|
89
|
+
mass: Double,
|
|
90
|
+
stiffness: Double,
|
|
91
|
+
damping: Double,
|
|
92
|
+
initialVelocity: Double,
|
|
93
|
+
inheritVelocity: Boolean,
|
|
94
|
+
reduceMotion: Double,
|
|
95
|
+
): Double = 0.0
|
|
96
|
+
|
|
97
|
+
override fun animateKeyframes(
|
|
98
|
+
driverId: Double,
|
|
99
|
+
hasInteractiveStart: Boolean,
|
|
100
|
+
startX: Double,
|
|
101
|
+
startY: Double,
|
|
102
|
+
startWidth: Double,
|
|
103
|
+
startHeight: Double,
|
|
104
|
+
startRadius: Double,
|
|
105
|
+
startContentTranslateX: Double,
|
|
106
|
+
startContentTranslateY: Double,
|
|
107
|
+
x: Double,
|
|
108
|
+
y: Double,
|
|
109
|
+
width: Double,
|
|
110
|
+
height: Double,
|
|
111
|
+
radius: Double,
|
|
112
|
+
contentTranslateX: Double,
|
|
113
|
+
contentTranslateY: Double,
|
|
114
|
+
durationMs: Double,
|
|
115
|
+
frames: ReadableArray,
|
|
116
|
+
reduceMotion: Double,
|
|
117
|
+
): Double = 0.0
|
|
118
|
+
|
|
119
|
+
override fun rejectAnimation(driverId: Double): Double = 0.0
|
|
120
|
+
|
|
121
|
+
override fun cancelAnimation(
|
|
122
|
+
driverId: Double,
|
|
123
|
+
animationId: Double,
|
|
124
|
+
behavior: Double,
|
|
125
|
+
): WritableArray = Arguments.createArray()
|
|
126
|
+
|
|
127
|
+
override fun destroyDriver(driverId: Double) = Unit
|
|
128
|
+
}
|
|
@@ -1,29 +1,46 @@
|
|
|
1
1
|
package com.smoothclipview
|
|
2
2
|
|
|
3
|
+
import android.content.res.Configuration
|
|
3
4
|
import android.graphics.Outline
|
|
4
5
|
import android.view.MotionEvent
|
|
5
6
|
import android.view.View
|
|
6
7
|
import android.view.ViewOutlineProvider
|
|
8
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
7
9
|
import com.facebook.react.uimanager.PixelUtil
|
|
8
10
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
11
|
import com.facebook.react.views.view.ReactViewGroup
|
|
10
12
|
import kotlin.math.roundToInt
|
|
11
13
|
|
|
12
14
|
class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
15
|
+
internal val contentContainer = ReactViewGroup(context)
|
|
13
16
|
private var requestedX = 0f
|
|
14
17
|
private var requestedY = 0f
|
|
15
18
|
private var requestedWidth = 0f
|
|
16
19
|
private var requestedHeight = 0f
|
|
17
20
|
private var requestedRadius = 0f
|
|
21
|
+
private var requestedContentTranslateX = 0f
|
|
22
|
+
private var requestedContentTranslateY = 0f
|
|
18
23
|
private var clipLeft = 0f
|
|
19
24
|
private var clipTop = 0f
|
|
20
25
|
private var clipRight = 0f
|
|
21
26
|
private var clipBottom = 0f
|
|
22
27
|
private var clipRadius = 0f
|
|
28
|
+
// Rounded edges actually emitted to the outline provider. Sub-pixel changes
|
|
29
|
+
// that round to the same integers must not invalidate the outline.
|
|
30
|
+
private var outlineLeft = 0
|
|
31
|
+
private var outlineTop = 0
|
|
32
|
+
private var outlineRight = 0
|
|
33
|
+
private var outlineBottom = 0
|
|
23
34
|
private var clipIsEmpty = true
|
|
24
35
|
private var requestedImportantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
25
36
|
private var acceptingTouchStream = false
|
|
26
37
|
|
|
38
|
+
/** Driver this view is registered with in the native registry (0 = none). */
|
|
39
|
+
internal var boundDriverId: Double = 0.0
|
|
40
|
+
|
|
41
|
+
/** Set once a legacy command arrives; later initialClip* props are ignored. */
|
|
42
|
+
internal var commandIsAuthoritative = false
|
|
43
|
+
|
|
27
44
|
private val clipOutlineProvider = object : ViewOutlineProvider() {
|
|
28
45
|
override fun getOutline(view: View, outline: Outline) {
|
|
29
46
|
if (clipIsEmpty) {
|
|
@@ -32,16 +49,17 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
outline.setRoundRect(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
outlineLeft,
|
|
53
|
+
outlineTop,
|
|
54
|
+
outlineRight,
|
|
55
|
+
outlineBottom,
|
|
39
56
|
clipRadius,
|
|
40
57
|
)
|
|
41
58
|
}
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
init {
|
|
62
|
+
super.addView(contentContainer)
|
|
45
63
|
outlineProvider = clipOutlineProvider
|
|
46
64
|
clipToOutline = true
|
|
47
65
|
visibility = INVISIBLE
|
|
@@ -54,9 +72,21 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
54
72
|
width: Double,
|
|
55
73
|
height: Double,
|
|
56
74
|
radius: Double,
|
|
75
|
+
) = setClipPresentationDip(x, y, width, height, radius, 0.0, 0.0)
|
|
76
|
+
|
|
77
|
+
@DoNotStrip
|
|
78
|
+
fun setClipPresentationDip(
|
|
79
|
+
x: Double,
|
|
80
|
+
y: Double,
|
|
81
|
+
width: Double,
|
|
82
|
+
height: Double,
|
|
83
|
+
radius: Double,
|
|
84
|
+
contentTranslateX: Double,
|
|
85
|
+
contentTranslateY: Double,
|
|
57
86
|
) {
|
|
58
87
|
if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
|
|
59
|
-
!height.isFinite() || !radius.isFinite()
|
|
88
|
+
!height.isFinite() || !radius.isFinite() ||
|
|
89
|
+
!contentTranslateX.isFinite() || !contentTranslateY.isFinite()
|
|
60
90
|
) {
|
|
61
91
|
return
|
|
62
92
|
}
|
|
@@ -66,8 +96,11 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
66
96
|
val nextWidth = PixelUtil.toPixelFromDIP(width).toFloat()
|
|
67
97
|
val nextHeight = PixelUtil.toPixelFromDIP(height).toFloat()
|
|
68
98
|
val nextRadius = PixelUtil.toPixelFromDIP(radius).toFloat()
|
|
99
|
+
val nextContentTranslateX = PixelUtil.toPixelFromDIP(contentTranslateX).toFloat()
|
|
100
|
+
val nextContentTranslateY = PixelUtil.toPixelFromDIP(contentTranslateY).toFloat()
|
|
69
101
|
if (!nextX.isFinite() || !nextY.isFinite() || !nextWidth.isFinite() ||
|
|
70
|
-
!nextHeight.isFinite() || !nextRadius.isFinite()
|
|
102
|
+
!nextHeight.isFinite() || !nextRadius.isFinite() ||
|
|
103
|
+
!nextContentTranslateX.isFinite() || !nextContentTranslateY.isFinite()
|
|
71
104
|
) {
|
|
72
105
|
return
|
|
73
106
|
}
|
|
@@ -77,9 +110,43 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
77
110
|
requestedWidth = nextWidth
|
|
78
111
|
requestedHeight = nextHeight
|
|
79
112
|
requestedRadius = nextRadius
|
|
113
|
+
if (requestedContentTranslateX != nextContentTranslateX) {
|
|
114
|
+
requestedContentTranslateX = nextContentTranslateX
|
|
115
|
+
contentContainer.translationX = nextContentTranslateX
|
|
116
|
+
}
|
|
117
|
+
if (requestedContentTranslateY != nextContentTranslateY) {
|
|
118
|
+
requestedContentTranslateY = nextContentTranslateY
|
|
119
|
+
contentContainer.translationY = nextContentTranslateY
|
|
120
|
+
}
|
|
80
121
|
applyRequestedGeometry()
|
|
81
122
|
}
|
|
82
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Driver hot path: called from the C++ registry with final pixel-space,
|
|
126
|
+
* pre-normalized values (see deliverToView in SmoothClipRegistry.cpp).
|
|
127
|
+
* No validation, scaling or normalization happens here.
|
|
128
|
+
*/
|
|
129
|
+
@DoNotStrip
|
|
130
|
+
fun setClipPresentationPx(
|
|
131
|
+
left: Float,
|
|
132
|
+
top: Float,
|
|
133
|
+
right: Float,
|
|
134
|
+
bottom: Float,
|
|
135
|
+
radius: Float,
|
|
136
|
+
contentTranslateX: Float,
|
|
137
|
+
contentTranslateY: Float,
|
|
138
|
+
) {
|
|
139
|
+
if (requestedContentTranslateX != contentTranslateX) {
|
|
140
|
+
requestedContentTranslateX = contentTranslateX
|
|
141
|
+
contentContainer.translationX = contentTranslateX
|
|
142
|
+
}
|
|
143
|
+
if (requestedContentTranslateY != contentTranslateY) {
|
|
144
|
+
requestedContentTranslateY = contentTranslateY
|
|
145
|
+
contentContainer.translationY = contentTranslateY
|
|
146
|
+
}
|
|
147
|
+
applyNormalizedClipPx(left, top, right, bottom, radius)
|
|
148
|
+
}
|
|
149
|
+
|
|
83
150
|
private fun applyRequestedGeometry() {
|
|
84
151
|
normalizeClipGeometryPx(
|
|
85
152
|
requestedX,
|
|
@@ -90,40 +157,98 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
90
157
|
width.toFloat(),
|
|
91
158
|
height.toFloat(),
|
|
92
159
|
) { left, top, right, bottom, radius ->
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
bottom != clipBottom || radius != clipRadius
|
|
160
|
+
applyNormalizedClipPx(left, top, right, bottom, radius)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
97
163
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
164
|
+
private fun applyNormalizedClipPx(
|
|
165
|
+
left: Float,
|
|
166
|
+
top: Float,
|
|
167
|
+
right: Float,
|
|
168
|
+
bottom: Float,
|
|
169
|
+
radius: Float,
|
|
170
|
+
) {
|
|
171
|
+
val isEmpty = right <= left || bottom <= top
|
|
172
|
+
val outlineGeometryChanged = outlineChanged(
|
|
173
|
+
left,
|
|
174
|
+
top,
|
|
175
|
+
right,
|
|
176
|
+
bottom,
|
|
177
|
+
radius,
|
|
178
|
+
outlineLeft,
|
|
179
|
+
outlineTop,
|
|
180
|
+
outlineRight,
|
|
181
|
+
outlineBottom,
|
|
182
|
+
clipRadius,
|
|
183
|
+
)
|
|
101
184
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (isEmpty) acceptingTouchStream = false
|
|
116
|
-
}
|
|
185
|
+
if (!outlineGeometryChanged && isEmpty == clipIsEmpty) {
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
clipLeft = left
|
|
190
|
+
clipTop = top
|
|
191
|
+
clipRight = right
|
|
192
|
+
clipBottom = bottom
|
|
193
|
+
clipRadius = radius
|
|
194
|
+
outlineLeft = left.roundToInt()
|
|
195
|
+
outlineTop = top.roundToInt()
|
|
196
|
+
outlineRight = right.roundToInt()
|
|
197
|
+
outlineBottom = bottom.roundToInt()
|
|
117
198
|
|
|
118
|
-
|
|
199
|
+
if (isEmpty != clipIsEmpty) {
|
|
200
|
+
clipIsEmpty = isEmpty
|
|
201
|
+
visibility = clipVisibility(isEmpty)
|
|
202
|
+
importantForAccessibility = clipAccessibility(
|
|
203
|
+
isEmpty,
|
|
204
|
+
requestedImportantForAccessibility,
|
|
205
|
+
)
|
|
206
|
+
if (isEmpty) acceptingTouchStream = false
|
|
119
207
|
}
|
|
208
|
+
|
|
209
|
+
invalidateOutline()
|
|
120
210
|
}
|
|
121
211
|
|
|
122
212
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
123
213
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
124
|
-
|
|
214
|
+
contentContainer.layout(0, 0, w, h)
|
|
215
|
+
if (boundDriverId != 0.0) {
|
|
216
|
+
// Driver deliveries are pre-normalized in C++ against the pushed
|
|
217
|
+
// host metrics; refresh them and let the registry redeliver the
|
|
218
|
+
// visible value synchronously.
|
|
219
|
+
SmoothClipBindings.nativeSetViewHostGeometry(
|
|
220
|
+
boundDriverId,
|
|
221
|
+
this,
|
|
222
|
+
densityScale(),
|
|
223
|
+
w.toDouble(),
|
|
224
|
+
h.toDouble(),
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
if (boundDriverId == 0.0 || commandIsAuthoritative) {
|
|
228
|
+
// Command geometry lives in the requested* DIP fields. Reapplying
|
|
229
|
+
// after the driver redelivery keeps the last-writer semantics: an
|
|
230
|
+
// authoritative command wins over the driver value on resize.
|
|
231
|
+
applyRequestedGeometry()
|
|
232
|
+
}
|
|
125
233
|
}
|
|
126
234
|
|
|
235
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
236
|
+
super.onConfigurationChanged(newConfig)
|
|
237
|
+
if (boundDriverId != 0.0) {
|
|
238
|
+
// Density can change without a size change (display switch); the
|
|
239
|
+
// registry scales DIP deliveries by the pushed density.
|
|
240
|
+
SmoothClipBindings.nativeSetViewHostGeometry(
|
|
241
|
+
boundDriverId,
|
|
242
|
+
this,
|
|
243
|
+
densityScale(),
|
|
244
|
+
width.toDouble(),
|
|
245
|
+
height.toDouble(),
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
internal fun densityScale(): Double = PixelUtil.toPixelFromDIP(1f).toDouble()
|
|
251
|
+
|
|
127
252
|
private fun containsRoundedPoint(x: Float, y: Float): Boolean {
|
|
128
253
|
return containsRoundedPointPx(
|
|
129
254
|
x,
|
|
@@ -179,11 +304,20 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
179
304
|
|
|
180
305
|
fun resetClipState() {
|
|
181
306
|
acceptingTouchStream = false
|
|
307
|
+
commandIsAuthoritative = false
|
|
182
308
|
requestedX = 0f
|
|
183
309
|
requestedY = 0f
|
|
184
310
|
requestedWidth = 0f
|
|
185
311
|
requestedHeight = 0f
|
|
186
312
|
requestedRadius = 0f
|
|
313
|
+
requestedContentTranslateX = 0f
|
|
314
|
+
requestedContentTranslateY = 0f
|
|
315
|
+
contentContainer.translationX = 0f
|
|
316
|
+
contentContainer.translationY = 0f
|
|
317
|
+
outlineLeft = 0
|
|
318
|
+
outlineTop = 0
|
|
319
|
+
outlineRight = 0
|
|
320
|
+
outlineBottom = 0
|
|
187
321
|
requestedImportantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
188
322
|
applyRequestedGeometry()
|
|
189
323
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.smoothclipview
|
|
2
2
|
|
|
3
|
+
import android.view.View
|
|
3
4
|
import com.facebook.react.module.annotations.ReactModule
|
|
4
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
6
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
@@ -20,10 +21,21 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
20
21
|
var width: Double = 0.0,
|
|
21
22
|
var height: Double = 0.0,
|
|
22
23
|
var radius: Double = 0.0,
|
|
24
|
+
var contentTranslateX: Double = 0.0,
|
|
25
|
+
var contentTranslateY: Double = 0.0,
|
|
26
|
+
var driverId: Double = 0.0,
|
|
27
|
+
// Whether any prop setter ran in the current update transaction.
|
|
28
|
+
var dirty: Boolean = false,
|
|
23
29
|
)
|
|
24
30
|
|
|
31
|
+
// Sticky per-view prop state: Fabric only delivers changed props, so a
|
|
32
|
+
// transaction touching one initialClip* prop must keep every other value
|
|
33
|
+
// — including driverId — from earlier transactions.
|
|
25
34
|
private val pendingInitialClips = java.util.WeakHashMap<SmoothClipView, PendingInitialClip>()
|
|
26
35
|
|
|
36
|
+
private fun pending(view: SmoothClipView): PendingInitialClip =
|
|
37
|
+
pendingInitialClips.getOrPut(view) { PendingInitialClip() }.also { it.dirty = true }
|
|
38
|
+
|
|
27
39
|
override fun getName(): String = NAME
|
|
28
40
|
|
|
29
41
|
override fun getDelegate(): ViewManagerDelegate<SmoothClipView> = delegate
|
|
@@ -31,6 +43,25 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
31
43
|
override fun createViewInstance(context: ThemedReactContext): SmoothClipView =
|
|
32
44
|
SmoothClipView(context).also { pendingInitialClips[it] = PendingInitialClip() }
|
|
33
45
|
|
|
46
|
+
@ReactProp(name = "driverId")
|
|
47
|
+
override fun setDriverId(view: SmoothClipView, value: Double) {
|
|
48
|
+
pending(view).driverId = value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun addView(parent: SmoothClipView, child: View, index: Int) {
|
|
52
|
+
parent.contentContainer.addView(child, index)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun getChildCount(parent: SmoothClipView): Int =
|
|
56
|
+
parent.contentContainer.childCount
|
|
57
|
+
|
|
58
|
+
override fun getChildAt(parent: SmoothClipView, index: Int): View =
|
|
59
|
+
parent.contentContainer.getChildAt(index)
|
|
60
|
+
|
|
61
|
+
override fun removeViewAt(parent: SmoothClipView, index: Int) {
|
|
62
|
+
parent.contentContainer.removeViewAt(index)
|
|
63
|
+
}
|
|
64
|
+
|
|
34
65
|
override fun setImportantForAccessibility(
|
|
35
66
|
view: SmoothClipView,
|
|
36
67
|
importantForAccessibility: String?,
|
|
@@ -41,27 +72,37 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
41
72
|
|
|
42
73
|
@ReactProp(name = "initialClipX")
|
|
43
74
|
override fun setInitialClipX(view: SmoothClipView, value: Double) {
|
|
44
|
-
|
|
75
|
+
pending(view).x = value
|
|
45
76
|
}
|
|
46
77
|
|
|
47
78
|
@ReactProp(name = "initialClipY")
|
|
48
79
|
override fun setInitialClipY(view: SmoothClipView, value: Double) {
|
|
49
|
-
|
|
80
|
+
pending(view).y = value
|
|
50
81
|
}
|
|
51
82
|
|
|
52
83
|
@ReactProp(name = "initialClipWidth")
|
|
53
84
|
override fun setInitialClipWidth(view: SmoothClipView, value: Double) {
|
|
54
|
-
|
|
85
|
+
pending(view).width = value
|
|
55
86
|
}
|
|
56
87
|
|
|
57
88
|
@ReactProp(name = "initialClipHeight")
|
|
58
89
|
override fun setInitialClipHeight(view: SmoothClipView, value: Double) {
|
|
59
|
-
|
|
90
|
+
pending(view).height = value
|
|
60
91
|
}
|
|
61
92
|
|
|
62
93
|
@ReactProp(name = "initialClipRadius")
|
|
63
94
|
override fun setInitialClipRadius(view: SmoothClipView, value: Double) {
|
|
64
|
-
|
|
95
|
+
pending(view).radius = value
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@ReactProp(name = "initialContentTranslateX")
|
|
99
|
+
override fun setInitialContentTranslateX(view: SmoothClipView, value: Double) {
|
|
100
|
+
pending(view).contentTranslateX = value
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@ReactProp(name = "initialContentTranslateY")
|
|
104
|
+
override fun setInitialContentTranslateY(view: SmoothClipView, value: Double) {
|
|
105
|
+
pending(view).contentTranslateY = value
|
|
65
106
|
}
|
|
66
107
|
|
|
67
108
|
override fun setClipGeometry(
|
|
@@ -72,28 +113,84 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
72
113
|
height: Double,
|
|
73
114
|
radius: Double,
|
|
74
115
|
) {
|
|
75
|
-
|
|
116
|
+
view.commandIsAuthoritative = true
|
|
76
117
|
view.setClipGeometryDip(x, y, width, height, radius)
|
|
77
118
|
}
|
|
78
119
|
|
|
120
|
+
override fun setClipPresentation(
|
|
121
|
+
view: SmoothClipView,
|
|
122
|
+
x: Double,
|
|
123
|
+
y: Double,
|
|
124
|
+
width: Double,
|
|
125
|
+
height: Double,
|
|
126
|
+
radius: Double,
|
|
127
|
+
contentTranslateX: Double,
|
|
128
|
+
contentTranslateY: Double,
|
|
129
|
+
) {
|
|
130
|
+
view.commandIsAuthoritative = true
|
|
131
|
+
view.setClipPresentationDip(
|
|
132
|
+
x,
|
|
133
|
+
y,
|
|
134
|
+
width,
|
|
135
|
+
height,
|
|
136
|
+
radius,
|
|
137
|
+
contentTranslateX,
|
|
138
|
+
contentTranslateY,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
79
142
|
override fun onAfterUpdateTransaction(view: SmoothClipView) {
|
|
80
143
|
super.onAfterUpdateTransaction(view)
|
|
81
|
-
val initial = pendingInitialClips
|
|
82
|
-
if (initial == null) {
|
|
144
|
+
val initial = pendingInitialClips[view]
|
|
145
|
+
if (initial == null || !initial.dirty) {
|
|
83
146
|
view.reapplyClipPresentation()
|
|
84
|
-
|
|
85
|
-
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
initial.dirty = false
|
|
150
|
+
if (view.boundDriverId != 0.0 && view.boundDriverId != initial.driverId) {
|
|
151
|
+
// The view moved to a different driver: leave the old driver's
|
|
152
|
+
// fanout before joining the new one.
|
|
153
|
+
SmoothClipBindings.nativeUnregisterView(view.boundDriverId, view)
|
|
154
|
+
}
|
|
155
|
+
view.boundDriverId = initial.driverId
|
|
156
|
+
if (initial.driverId != 0.0) {
|
|
157
|
+
SmoothClipBindings.nativeRegisterView(
|
|
158
|
+
initial.driverId,
|
|
159
|
+
view,
|
|
86
160
|
initial.x,
|
|
87
161
|
initial.y,
|
|
88
162
|
initial.width,
|
|
89
163
|
initial.height,
|
|
90
164
|
initial.radius,
|
|
165
|
+
initial.contentTranslateX,
|
|
166
|
+
initial.contentTranslateY,
|
|
167
|
+
view.densityScale(),
|
|
168
|
+
view.width.toDouble(),
|
|
169
|
+
view.height.toDouble(),
|
|
91
170
|
)
|
|
171
|
+
} else if (!view.commandIsAuthoritative) {
|
|
172
|
+
// Command-driven mode (driverId 0): the initial props are the only
|
|
173
|
+
// source of the first clip, so apply them directly.
|
|
174
|
+
view.setClipPresentationDip(
|
|
175
|
+
initial.x,
|
|
176
|
+
initial.y,
|
|
177
|
+
initial.width,
|
|
178
|
+
initial.height,
|
|
179
|
+
initial.radius,
|
|
180
|
+
initial.contentTranslateX,
|
|
181
|
+
initial.contentTranslateY,
|
|
182
|
+
)
|
|
183
|
+
} else {
|
|
184
|
+
view.reapplyClipPresentation()
|
|
92
185
|
}
|
|
93
186
|
}
|
|
94
187
|
|
|
95
188
|
override fun onDropViewInstance(view: SmoothClipView) {
|
|
96
189
|
pendingInitialClips.remove(view)
|
|
190
|
+
if (view.boundDriverId != 0.0) {
|
|
191
|
+
SmoothClipBindings.nativeUnregisterView(view.boundDriverId, view)
|
|
192
|
+
view.boundDriverId = 0.0
|
|
193
|
+
}
|
|
97
194
|
view.resetClipState()
|
|
98
195
|
super.onDropViewInstance(view)
|
|
99
196
|
}
|