react-native-smooth-clip-view 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -388
- package/android/build.gradle +3 -3
- package/android/proguard-rules.pro +2 -2
- package/android/src/main/cpp/SmoothClipAndroid.h +9 -12
- package/android/src/main/cpp/SmoothClipBindings.cpp +300 -704
- package/android/src/main/cpp/SmoothClipRegistry.cpp +348 -594
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +36 -227
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +16 -19
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +36 -294
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +344 -488
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +75 -77
- package/cpp/SmoothClipAnimationCurve.h +237 -346
- package/cpp/SmoothClipRegistry.h +24 -52
- package/cpp/SmoothClipSharedGeometry.h +64 -94
- package/cpp/SmoothClipVelocityTracker.h +5 -5
- package/ios/SmoothClipGeometry.h +23 -34
- package/ios/SmoothClipRegistry.mm +206 -711
- package/ios/SmoothClipTurboModule.cpp +217 -715
- package/ios/SmoothClipTurboModule.h +54 -297
- package/ios/SmoothClipView.mm +599 -505
- package/ios/SmoothClipViewRegistry.h +2 -7
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +84 -17
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -1
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +20 -26
- package/lib/module/capabilities.native.js +1 -19
- package/lib/module/capabilities.native.js.map +1 -1
- package/lib/module/capabilityTypes.js +0 -9
- package/lib/module/capabilityTypes.js.map +1 -1
- package/lib/module/controllerInternals.js +28 -0
- package/lib/module/controllerInternals.js.map +1 -0
- package/lib/module/controllerLifecycle.js +9 -0
- package/lib/module/controllerLifecycle.js.map +1 -0
- package/lib/module/controllerTypes.js +4 -0
- package/lib/module/controllerTypes.js.map +1 -0
- package/lib/module/controllers.android.js +4 -0
- package/lib/module/controllers.android.js.map +1 -0
- package/lib/module/controllers.ios.js +4 -0
- package/lib/module/controllers.ios.js.map +1 -0
- package/lib/module/controllers.js +4 -0
- package/lib/module/controllers.js.map +1 -0
- package/lib/module/controllers.native.js +89 -0
- package/lib/module/controllers.native.js.map +1 -0
- package/lib/module/geometry.js +71 -51
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/{groupDriverTypes.js.map → groupTypes.js.map} +1 -1
- package/lib/module/groups.android.js +4 -0
- package/lib/module/groups.android.js.map +1 -0
- package/lib/module/groups.ios.js +4 -0
- package/lib/module/groups.ios.js.map +1 -0
- package/lib/module/groups.js +4 -0
- package/lib/module/groups.js.map +1 -0
- package/lib/module/groups.native.js +399 -0
- package/lib/module/groups.native.js.map +1 -0
- package/lib/module/ids.js +8 -0
- package/lib/module/ids.js.map +1 -0
- package/lib/module/index.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeCompletion.js +37 -16
- package/lib/module/nativeCompletion.js.map +1 -1
- package/lib/module/presentationCodec.js +81 -0
- package/lib/module/presentationCodec.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +14 -23
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +18 -14
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +10 -5
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.native.d.ts +1 -1
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -1
- package/lib/typescript/src/capabilityTypes.d.ts +0 -6
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -1
- package/lib/typescript/src/controllerInternals.d.ts +14 -0
- package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
- package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
- package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/controllerTypes.d.ts +62 -0
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllers.android.d.ts +2 -0
- package/lib/typescript/src/controllers.android.d.ts.map +1 -0
- package/lib/typescript/src/controllers.d.ts +2 -0
- package/lib/typescript/src/controllers.d.ts.map +1 -0
- package/lib/typescript/src/controllers.ios.d.ts +2 -0
- package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
- package/lib/typescript/src/controllers.native.d.ts +3 -0
- package/lib/typescript/src/controllers.native.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +22 -12
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupTypes.d.ts +31 -0
- package/lib/typescript/src/groupTypes.d.ts.map +1 -0
- package/lib/typescript/src/groups.android.d.ts +2 -0
- package/lib/typescript/src/groups.android.d.ts.map +1 -0
- package/lib/typescript/src/groups.d.ts +2 -0
- package/lib/typescript/src/groups.d.ts.map +1 -0
- package/lib/typescript/src/groups.ios.d.ts +2 -0
- package/lib/typescript/src/groups.ios.d.ts.map +1 -0
- package/lib/typescript/src/groups.native.d.ts +3 -0
- package/lib/typescript/src/groups.native.d.ts.map +1 -0
- package/lib/typescript/src/ids.d.ts +2 -0
- package/lib/typescript/src/ids.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -6
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +16 -6
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +10 -0
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/NativeSmoothClipModule.ts +41 -292
- package/src/SmoothClipView.ios.tsx +130 -23
- package/src/SmoothClipView.tsx +5 -1
- package/src/SmoothClipViewNativeComponent.ts +20 -26
- package/src/capabilities.native.ts +2 -52
- package/src/capabilityTypes.ts +0 -14
- package/src/controllerInternals.ts +52 -0
- package/src/controllerLifecycle.ts +6 -0
- package/src/controllerTypes.ts +87 -0
- package/src/controllers.android.ts +1 -0
- package/src/controllers.ios.ts +1 -0
- package/src/controllers.native.ts +104 -0
- package/src/controllers.ts +1 -0
- package/src/geometry.ts +118 -74
- package/src/groupTypes.ts +53 -0
- package/src/groups.android.ts +1 -0
- package/src/groups.ios.ts +1 -0
- package/src/groups.native.ts +604 -0
- package/src/groups.ts +1 -0
- package/src/ids.ts +6 -0
- package/src/index.ts +20 -28
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/driverState.js +0 -82
- package/lib/module/driverState.js.map +0 -1
- package/lib/module/driverTypes.js +0 -4
- package/lib/module/driverTypes.js.map +0 -1
- package/lib/module/drivers.android.js +0 -6
- package/lib/module/drivers.android.js.map +0 -1
- package/lib/module/drivers.ios.js +0 -6
- package/lib/module/drivers.ios.js.map +0 -1
- package/lib/module/drivers.js +0 -4
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -756
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/groupDriverTypes.js +0 -4
- package/lib/module/groupDrivers.android.js +0 -4
- package/lib/module/groupDrivers.android.js.map +0 -1
- package/lib/module/groupDrivers.ios.js +0 -4
- package/lib/module/groupDrivers.ios.js.map +0 -1
- package/lib/module/groupDrivers.js +0 -4
- package/lib/module/groupDrivers.js.map +0 -1
- package/lib/module/groupDrivers.native.js +0 -799
- package/lib/module/groupDrivers.native.js.map +0 -1
- package/lib/module/presentationProtocol.js +0 -15
- package/lib/module/presentationProtocol.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -26
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -121
- package/lib/typescript/src/driverTypes.d.ts.map +0 -1
- package/lib/typescript/src/drivers.android.d.ts +0 -3
- package/lib/typescript/src/drivers.android.d.ts.map +0 -1
- package/lib/typescript/src/drivers.d.ts +0 -3
- package/lib/typescript/src/drivers.d.ts.map +0 -1
- package/lib/typescript/src/drivers.ios.d.ts +0 -3
- package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/drivers.native.d.ts +0 -5
- package/lib/typescript/src/drivers.native.d.ts.map +0 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +0 -66
- package/lib/typescript/src/groupDriverTypes.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.android.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.android.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.ios.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.native.d.ts +0 -4
- package/lib/typescript/src/groupDrivers.native.d.ts.map +0 -1
- package/lib/typescript/src/presentationProtocol.d.ts +0 -4
- package/lib/typescript/src/presentationProtocol.d.ts.map +0 -1
- package/lib/typescript/src/reactRequests.d.ts +0 -9
- package/lib/typescript/src/reactRequests.d.ts.map +0 -1
- package/src/driverState.ts +0 -118
- package/src/driverTypes.ts +0 -171
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -1599
- package/src/drivers.ts +0 -6
- package/src/groupDriverTypes.ts +0 -117
- package/src/groupDrivers.android.ts +0 -1
- package/src/groupDrivers.ios.ts +0 -1
- package/src/groupDrivers.native.ts +0 -1189
- package/src/groupDrivers.ts +0 -1
- package/src/presentationProtocol.ts +0 -28
- package/src/reactRequests.ts +0 -76
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
package com.smoothclipview
|
|
2
2
|
|
|
3
3
|
import android.content.res.Configuration
|
|
4
|
+
import android.graphics.BlurMaskFilter
|
|
5
|
+
import android.graphics.Canvas
|
|
6
|
+
import android.graphics.Color
|
|
4
7
|
import android.graphics.Outline
|
|
8
|
+
import android.graphics.Paint
|
|
5
9
|
import android.graphics.Path
|
|
10
|
+
import android.graphics.Region
|
|
11
|
+
import android.os.Build
|
|
6
12
|
import android.os.Trace
|
|
7
13
|
import android.view.MotionEvent
|
|
8
14
|
import android.view.View
|
|
@@ -11,14 +17,17 @@ import com.facebook.proguard.annotations.DoNotStrip
|
|
|
11
17
|
import com.facebook.react.uimanager.PixelUtil
|
|
12
18
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
13
19
|
import com.facebook.react.views.view.ReactViewGroup
|
|
20
|
+
import kotlin.math.roundToInt
|
|
21
|
+
import kotlin.math.abs
|
|
22
|
+
import kotlin.math.pow
|
|
14
23
|
|
|
15
24
|
class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
25
|
+
private val clipContainer = ReactViewGroup(context)
|
|
16
26
|
internal val contentContainer = ReactViewGroup(context)
|
|
17
27
|
private var requestedX = 0f
|
|
18
28
|
private var requestedY = 0f
|
|
19
29
|
private var requestedWidth = 0f
|
|
20
30
|
private var requestedHeight = 0f
|
|
21
|
-
private var requestedRadius = 0f
|
|
22
31
|
private var requestedTopLeftRadius = 0f
|
|
23
32
|
private var requestedTopRightRadius = 0f
|
|
24
33
|
private var requestedBottomRightRadius = 0f
|
|
@@ -27,43 +36,36 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
27
36
|
private var requestedContentTranslateX = 0f
|
|
28
37
|
private var requestedContentTranslateY = 0f
|
|
29
38
|
private var requestedContentScale = 1f
|
|
30
|
-
private var
|
|
39
|
+
private var requestedShadowEnabled = false
|
|
40
|
+
private var requestedShadowRed = 0f
|
|
41
|
+
private var requestedShadowGreen = 0f
|
|
42
|
+
private var requestedShadowBlue = 0f
|
|
43
|
+
private var requestedShadowAlpha = 1f
|
|
44
|
+
private var requestedShadowOffsetX = 0f
|
|
45
|
+
private var requestedShadowOffsetY = 0f
|
|
46
|
+
private var requestedShadowBlurRadius = 0f
|
|
47
|
+
private var requestedShadowSpreadDistance = 0f
|
|
31
48
|
private var clipLeft = 0f
|
|
32
49
|
private var clipTop = 0f
|
|
33
50
|
private var clipRight = 0f
|
|
34
51
|
private var clipBottom = 0f
|
|
35
|
-
private var clipRadius = 0f
|
|
36
52
|
private var clipTopLeftRadius = 0f
|
|
37
53
|
private var clipTopRightRadius = 0f
|
|
38
54
|
private var clipBottomRightRadius = 0f
|
|
39
55
|
private var clipBottomLeftRadius = 0f
|
|
40
56
|
private var clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
41
57
|
private val clipPath = Path()
|
|
42
|
-
private var
|
|
43
|
-
private var
|
|
44
|
-
// Rounded edges actually emitted to the outline provider. Sub-pixel changes
|
|
45
|
-
// that round to the same integers must not invalidate the outline.
|
|
46
|
-
private var outlineLeft = 0
|
|
47
|
-
private var outlineTop = 0
|
|
48
|
-
private var outlineRight = 0
|
|
49
|
-
private var outlineBottom = 0
|
|
58
|
+
private var boxShadowPath: Path? = null
|
|
59
|
+
private var boxShadowPaint: Paint? = null
|
|
50
60
|
private var clipIsEmpty = true
|
|
51
|
-
// Sub-pixel remainder of the clip origin, carried on this view's own
|
|
52
|
-
// translation and subtracted back out of the content container.
|
|
53
|
-
private var clipResidualX = 0f
|
|
54
|
-
private var clipResidualY = 0f
|
|
55
|
-
// The consumer's `transform` translation, kept apart from the residual so
|
|
56
|
-
// the two compose instead of clobbering each other.
|
|
57
|
-
private var userTranslationX = 0f
|
|
58
|
-
private var userTranslationY = 0f
|
|
59
61
|
private var requestedImportantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
62
|
+
private var autonomousMotion = false
|
|
60
63
|
private var acceptingTouchStream = false
|
|
61
|
-
private var retainedTouchEvent: MotionEvent? = null
|
|
62
64
|
|
|
63
65
|
/** Driver this view is registered with in the native registry (0 = none). */
|
|
64
66
|
internal var boundDriverId: Double = 0.0
|
|
65
67
|
|
|
66
|
-
/** Set once a
|
|
68
|
+
/** Set once a command arrives; later initial presentation props are ignored. */
|
|
67
69
|
internal var commandIsAuthoritative = false
|
|
68
70
|
|
|
69
71
|
private val clipOutlineProvider = object : ViewOutlineProvider() {
|
|
@@ -73,93 +75,26 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
73
75
|
return
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
}
|
|
78
|
+
// Outline.setRoundRect only accepts integer edges. The raw path
|
|
79
|
+
// keeps sub-pixel coordinates and off-host geometry intact.
|
|
80
|
+
outline.setPath(clipPath)
|
|
93
81
|
}
|
|
94
82
|
}
|
|
95
83
|
|
|
96
84
|
init {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
clipContainer.addView(contentContainer)
|
|
86
|
+
super.addView(clipContainer)
|
|
87
|
+
clipContainer.outlineProvider = clipOutlineProvider
|
|
88
|
+
clipContainer.clipToOutline = true
|
|
89
|
+
clipToOutline = false
|
|
90
|
+
setWillNotDraw(false)
|
|
100
91
|
visibility = INVISIBLE
|
|
101
92
|
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
102
93
|
}
|
|
103
94
|
|
|
104
|
-
|
|
105
|
-
x: Double,
|
|
106
|
-
y: Double,
|
|
107
|
-
width: Double,
|
|
108
|
-
height: Double,
|
|
109
|
-
radius: Double,
|
|
110
|
-
) = setClipPresentationDip(x, y, width, height, radius, 0.0, 0.0)
|
|
111
|
-
|
|
95
|
+
/** DIP fallback used until a registered view has pushed host metrics. */
|
|
112
96
|
@DoNotStrip
|
|
113
97
|
fun setClipPresentationDip(
|
|
114
|
-
x: Double,
|
|
115
|
-
y: Double,
|
|
116
|
-
width: Double,
|
|
117
|
-
height: Double,
|
|
118
|
-
radius: Double,
|
|
119
|
-
contentTranslateX: Double,
|
|
120
|
-
contentTranslateY: Double,
|
|
121
|
-
) {
|
|
122
|
-
if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
|
|
123
|
-
!height.isFinite() || !radius.isFinite() ||
|
|
124
|
-
!contentTranslateX.isFinite() || !contentTranslateY.isFinite()
|
|
125
|
-
) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
val nextX = PixelUtil.toPixelFromDIP(x).toFloat()
|
|
130
|
-
val nextY = PixelUtil.toPixelFromDIP(y).toFloat()
|
|
131
|
-
val nextWidth = PixelUtil.toPixelFromDIP(width).toFloat()
|
|
132
|
-
val nextHeight = PixelUtil.toPixelFromDIP(height).toFloat()
|
|
133
|
-
val nextRadius = PixelUtil.toPixelFromDIP(radius).toFloat()
|
|
134
|
-
val nextContentTranslateX = PixelUtil.toPixelFromDIP(contentTranslateX).toFloat()
|
|
135
|
-
val nextContentTranslateY = PixelUtil.toPixelFromDIP(contentTranslateY).toFloat()
|
|
136
|
-
if (!nextX.isFinite() || !nextY.isFinite() || !nextWidth.isFinite() ||
|
|
137
|
-
!nextHeight.isFinite() || !nextRadius.isFinite() ||
|
|
138
|
-
!nextContentTranslateX.isFinite() || !nextContentTranslateY.isFinite()
|
|
139
|
-
) {
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
requestedX = nextX
|
|
144
|
-
requestedY = nextY
|
|
145
|
-
requestedWidth = nextWidth
|
|
146
|
-
requestedHeight = nextHeight
|
|
147
|
-
requestedRadius = nextRadius
|
|
148
|
-
requestedTopLeftRadius = nextRadius
|
|
149
|
-
requestedTopRightRadius = nextRadius
|
|
150
|
-
requestedBottomRightRadius = nextRadius
|
|
151
|
-
requestedBottomLeftRadius = nextRadius
|
|
152
|
-
requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
153
|
-
requestedContentTranslateX = nextContentTranslateX
|
|
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
98
|
x: Double,
|
|
164
99
|
y: Double,
|
|
165
100
|
width: Double,
|
|
@@ -172,13 +107,29 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
172
107
|
contentTranslateX: Double,
|
|
173
108
|
contentTranslateY: Double,
|
|
174
109
|
contentScale: Double,
|
|
110
|
+
shadowEnabled: Boolean = false,
|
|
111
|
+
shadowRed: Double = 0.0,
|
|
112
|
+
shadowGreen: Double = 0.0,
|
|
113
|
+
shadowBlue: Double = 0.0,
|
|
114
|
+
shadowAlpha: Double = 1.0,
|
|
115
|
+
shadowOffsetX: Double = 0.0,
|
|
116
|
+
shadowOffsetY: Double = 0.0,
|
|
117
|
+
shadowBlurRadius: Double = 0.0,
|
|
118
|
+
shadowSpreadDistance: Double = 0.0,
|
|
175
119
|
) {
|
|
176
120
|
if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
|
|
177
121
|
!height.isFinite() || !topLeftRadius.isFinite() ||
|
|
178
122
|
!topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
|
|
179
123
|
!bottomLeftRadius.isFinite() || !contentTranslateX.isFinite() ||
|
|
180
124
|
!contentTranslateY.isFinite() || !contentScale.isFinite() ||
|
|
181
|
-
|
|
125
|
+
!shadowRed.isFinite() || !shadowGreen.isFinite() ||
|
|
126
|
+
!shadowBlue.isFinite() || !shadowAlpha.isFinite() ||
|
|
127
|
+
!shadowOffsetX.isFinite() ||
|
|
128
|
+
!shadowOffsetY.isFinite() || !shadowBlurRadius.isFinite() ||
|
|
129
|
+
!shadowSpreadDistance.isFinite() || contentScale <= 0.0 ||
|
|
130
|
+
shadowRed !in 0.0..1.0 || shadowGreen !in 0.0..1.0 ||
|
|
131
|
+
shadowBlue !in 0.0..1.0 || shadowAlpha !in 0.0..1.0 ||
|
|
132
|
+
shadowBlurRadius < 0.0 ||
|
|
182
133
|
(curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
|
|
183
134
|
) {
|
|
184
135
|
return
|
|
@@ -195,12 +146,19 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
195
146
|
val nextContentTranslateX = PixelUtil.toPixelFromDIP(contentTranslateX).toFloat()
|
|
196
147
|
val nextContentTranslateY = PixelUtil.toPixelFromDIP(contentTranslateY).toFloat()
|
|
197
148
|
val nextContentScale = contentScale.toFloat()
|
|
149
|
+
val nextShadowOffsetX = PixelUtil.toPixelFromDIP(shadowOffsetX).toFloat()
|
|
150
|
+
val nextShadowOffsetY = PixelUtil.toPixelFromDIP(shadowOffsetY).toFloat()
|
|
151
|
+
val nextShadowBlurRadius = PixelUtil.toPixelFromDIP(shadowBlurRadius).toFloat()
|
|
152
|
+
val nextShadowSpreadDistance =
|
|
153
|
+
PixelUtil.toPixelFromDIP(shadowSpreadDistance).toFloat()
|
|
198
154
|
if (!nextX.isFinite() || !nextY.isFinite() || !nextWidth.isFinite() ||
|
|
199
155
|
!nextHeight.isFinite() || !nextTopLeftRadius.isFinite() ||
|
|
200
156
|
!nextTopRightRadius.isFinite() || !nextBottomRightRadius.isFinite() ||
|
|
201
157
|
!nextBottomLeftRadius.isFinite() || !nextContentTranslateX.isFinite() ||
|
|
202
158
|
!nextContentTranslateY.isFinite() || !nextContentScale.isFinite() ||
|
|
203
|
-
|
|
159
|
+
!nextShadowOffsetX.isFinite() || !nextShadowOffsetY.isFinite() ||
|
|
160
|
+
!nextShadowBlurRadius.isFinite() ||
|
|
161
|
+
!nextShadowSpreadDistance.isFinite() || nextContentScale <= 0f
|
|
204
162
|
) {
|
|
205
163
|
return
|
|
206
164
|
}
|
|
@@ -209,7 +167,6 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
209
167
|
requestedY = nextY
|
|
210
168
|
requestedWidth = nextWidth
|
|
211
169
|
requestedHeight = nextHeight
|
|
212
|
-
requestedRadius = 0f
|
|
213
170
|
requestedTopLeftRadius = nextTopLeftRadius
|
|
214
171
|
requestedTopRightRadius = nextTopRightRadius
|
|
215
172
|
requestedBottomRightRadius = nextBottomRightRadius
|
|
@@ -218,47 +175,23 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
218
175
|
requestedContentTranslateX = nextContentTranslateX
|
|
219
176
|
requestedContentTranslateY = nextContentTranslateY
|
|
220
177
|
requestedContentScale = nextContentScale
|
|
221
|
-
|
|
178
|
+
storeShadow(
|
|
179
|
+
shadowEnabled,
|
|
180
|
+
shadowRed.toFloat(),
|
|
181
|
+
shadowGreen.toFloat(),
|
|
182
|
+
shadowBlue.toFloat(),
|
|
183
|
+
shadowAlpha.toFloat(),
|
|
184
|
+
nextShadowOffsetX,
|
|
185
|
+
nextShadowOffsetY,
|
|
186
|
+
nextShadowBlurRadius,
|
|
187
|
+
nextShadowSpreadDistance,
|
|
188
|
+
)
|
|
222
189
|
applyRequestedGeometry()
|
|
223
190
|
}
|
|
224
191
|
|
|
225
|
-
/**
|
|
226
|
-
* Driver hot path: called from the C++ registry with final pixel-space,
|
|
227
|
-
* pre-normalized values (see deliverToView in SmoothClipRegistry.cpp).
|
|
228
|
-
* No validation, scaling or normalization happens here.
|
|
229
|
-
*/
|
|
192
|
+
/** Driver hot path. Geometry is canonical, raw, and host-independent. */
|
|
230
193
|
@DoNotStrip
|
|
231
194
|
fun setClipPresentationPx(
|
|
232
|
-
left: Float,
|
|
233
|
-
top: Float,
|
|
234
|
-
right: Float,
|
|
235
|
-
bottom: Float,
|
|
236
|
-
radius: Float,
|
|
237
|
-
contentTranslateX: Float,
|
|
238
|
-
contentTranslateY: Float,
|
|
239
|
-
) {
|
|
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
195
|
left: Float,
|
|
263
196
|
top: Float,
|
|
264
197
|
right: Float,
|
|
@@ -271,24 +204,51 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
271
204
|
contentTranslateX: Float,
|
|
272
205
|
contentTranslateY: Float,
|
|
273
206
|
contentScale: Float,
|
|
207
|
+
shadowEnabled: Boolean = false,
|
|
208
|
+
shadowRed: Float = 0f,
|
|
209
|
+
shadowGreen: Float = 0f,
|
|
210
|
+
shadowBlue: Float = 0f,
|
|
211
|
+
shadowAlpha: Float = 1f,
|
|
212
|
+
shadowOffsetX: Float = 0f,
|
|
213
|
+
shadowOffsetY: Float = 0f,
|
|
214
|
+
shadowBlurRadius: Float = 0f,
|
|
215
|
+
shadowSpreadDistance: Float = 0f,
|
|
274
216
|
) {
|
|
275
217
|
if (!left.isFinite() || !top.isFinite() || !right.isFinite() ||
|
|
276
218
|
!bottom.isFinite() || !topLeftRadius.isFinite() ||
|
|
277
219
|
!topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
|
|
278
220
|
!bottomLeftRadius.isFinite() || !contentTranslateX.isFinite() ||
|
|
279
221
|
!contentTranslateY.isFinite() || !contentScale.isFinite() ||
|
|
280
|
-
|
|
222
|
+
!shadowRed.isFinite() || !shadowGreen.isFinite() ||
|
|
223
|
+
!shadowBlue.isFinite() || !shadowAlpha.isFinite() ||
|
|
224
|
+
!shadowOffsetX.isFinite() ||
|
|
225
|
+
!shadowOffsetY.isFinite() || !shadowBlurRadius.isFinite() ||
|
|
226
|
+
!shadowSpreadDistance.isFinite() || contentScale <= 0f ||
|
|
227
|
+
shadowRed !in 0f..1f || shadowGreen !in 0f..1f ||
|
|
228
|
+
shadowBlue !in 0f..1f || shadowAlpha !in 0f..1f ||
|
|
229
|
+
shadowBlurRadius < 0f ||
|
|
281
230
|
(curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
|
|
282
231
|
) {
|
|
283
232
|
return
|
|
284
233
|
}
|
|
285
234
|
|
|
286
|
-
if (BuildConfig.DEBUG) Trace.beginSection("SmoothClip.
|
|
235
|
+
if (BuildConfig.DEBUG) Trace.beginSection("SmoothClip.applyPresentationPx")
|
|
287
236
|
try {
|
|
288
237
|
requestedContentTranslateX = contentTranslateX
|
|
289
238
|
requestedContentTranslateY = contentTranslateY
|
|
290
239
|
requestedContentScale = contentScale
|
|
291
|
-
|
|
240
|
+
storeShadow(
|
|
241
|
+
shadowEnabled,
|
|
242
|
+
shadowRed,
|
|
243
|
+
shadowGreen,
|
|
244
|
+
shadowBlue,
|
|
245
|
+
shadowAlpha,
|
|
246
|
+
shadowOffsetX,
|
|
247
|
+
shadowOffsetY,
|
|
248
|
+
shadowBlurRadius,
|
|
249
|
+
shadowSpreadDistance,
|
|
250
|
+
)
|
|
251
|
+
applyCanonicalClipPx(
|
|
292
252
|
left,
|
|
293
253
|
top,
|
|
294
254
|
right,
|
|
@@ -305,135 +265,155 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
305
265
|
}
|
|
306
266
|
|
|
307
267
|
private fun applyRequestedGeometry() {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
}
|
|
268
|
+
canonicalizeClipGeometryPx(
|
|
269
|
+
requestedX,
|
|
270
|
+
requestedY,
|
|
271
|
+
requestedWidth,
|
|
272
|
+
requestedHeight,
|
|
273
|
+
requestedTopLeftRadius,
|
|
274
|
+
requestedTopRightRadius,
|
|
275
|
+
requestedBottomRightRadius,
|
|
276
|
+
requestedBottomLeftRadius,
|
|
277
|
+
requestedCurveCode,
|
|
278
|
+
) { left, top, right, bottom, topLeft, topRight, bottomRight, bottomLeft, curve ->
|
|
279
|
+
applyCanonicalClipPx(
|
|
280
|
+
left,
|
|
281
|
+
top,
|
|
282
|
+
right,
|
|
283
|
+
bottom,
|
|
284
|
+
topLeft,
|
|
285
|
+
topRight,
|
|
286
|
+
bottomRight,
|
|
287
|
+
bottomLeft,
|
|
288
|
+
curve,
|
|
289
|
+
)
|
|
346
290
|
}
|
|
347
291
|
}
|
|
348
292
|
|
|
349
|
-
private fun
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
293
|
+
private fun storeShadow(
|
|
294
|
+
enabled: Boolean,
|
|
295
|
+
red: Float,
|
|
296
|
+
green: Float,
|
|
297
|
+
blue: Float,
|
|
298
|
+
alpha: Float,
|
|
299
|
+
offsetX: Float,
|
|
300
|
+
offsetY: Float,
|
|
301
|
+
blurRadius: Float,
|
|
302
|
+
spreadDistance: Float,
|
|
355
303
|
) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
val
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
304
|
+
val wasVisible = requestedShadowEnabled && requestedShadowAlpha > 0f
|
|
305
|
+
val isVisible = enabled && alpha > 0f
|
|
306
|
+
val paintChanged = red != requestedShadowRed ||
|
|
307
|
+
green != requestedShadowGreen || blue != requestedShadowBlue ||
|
|
308
|
+
alpha != requestedShadowAlpha || blurRadius != requestedShadowBlurRadius
|
|
309
|
+
val pathChanged = offsetX != requestedShadowOffsetX ||
|
|
310
|
+
offsetY != requestedShadowOffsetY ||
|
|
311
|
+
spreadDistance != requestedShadowSpreadDistance
|
|
312
|
+
requestedShadowEnabled = enabled
|
|
313
|
+
requestedShadowRed = red
|
|
314
|
+
requestedShadowGreen = green
|
|
315
|
+
requestedShadowBlue = blue
|
|
316
|
+
requestedShadowAlpha = alpha
|
|
317
|
+
requestedShadowOffsetX = offsetX
|
|
318
|
+
requestedShadowOffsetY = offsetY
|
|
319
|
+
requestedShadowBlurRadius = blurRadius
|
|
320
|
+
requestedShadowSpreadDistance = spreadDistance
|
|
321
|
+
if (!isVisible) {
|
|
322
|
+
if (wasVisible) invalidate()
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
if (paintChanged || boxShadowPaint == null) updateBoxShadowPaint()
|
|
326
|
+
if (pathChanged || !wasVisible || boxShadowPath == null) {
|
|
327
|
+
rebuildBoxShadowPath()
|
|
328
|
+
}
|
|
329
|
+
if (paintChanged || pathChanged || !wasVisible) invalidate()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private fun updateBoxShadowPaint() {
|
|
333
|
+
val paint = boxShadowPaint ?: Paint(Paint.ANTI_ALIAS_FLAG).also {
|
|
334
|
+
boxShadowPaint = it
|
|
335
|
+
}
|
|
336
|
+
paint.color = Color.argb(
|
|
337
|
+
(requestedShadowAlpha * 255f).roundToInt(),
|
|
338
|
+
(requestedShadowRed * 255f).roundToInt(),
|
|
339
|
+
(requestedShadowGreen * 255f).roundToInt(),
|
|
340
|
+
(requestedShadowBlue * 255f).roundToInt(),
|
|
384
341
|
)
|
|
342
|
+
val sigmaPx = requestedShadowBlurRadius * 0.5f
|
|
343
|
+
val maskRadius = if (sigmaPx > 0.5f) {
|
|
344
|
+
(sigmaPx - 0.5f) / 0.57735f
|
|
345
|
+
} else {
|
|
346
|
+
0f
|
|
347
|
+
}
|
|
348
|
+
paint.maskFilter = if (maskRadius > 0f) {
|
|
349
|
+
BlurMaskFilter(maskRadius, BlurMaskFilter.Blur.NORMAL)
|
|
350
|
+
} else {
|
|
351
|
+
null
|
|
352
|
+
}
|
|
353
|
+
}
|
|
385
354
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
clipTopRightRadius = radius
|
|
393
|
-
clipBottomRightRadius = radius
|
|
394
|
-
clipBottomLeftRadius = radius
|
|
395
|
-
clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
396
|
-
// Whatever integer rounding threw away, carried on this view's own
|
|
397
|
-
// translation so the clip edge still lands where the driver asked
|
|
398
|
-
// instead of snapping to the pixel grid while the content it clips
|
|
399
|
-
// slides in floats. It is a single scalar per axis only because the far
|
|
400
|
-
// edges are derived from the origin — with independently rounded edges
|
|
401
|
-
// no one translation could place both.
|
|
402
|
-
clipResidualX = left - nextOutlineLeft
|
|
403
|
-
clipResidualY = top - nextOutlineTop
|
|
404
|
-
applyClipPlacement()
|
|
405
|
-
|
|
406
|
-
if (isEmpty != clipIsEmpty) {
|
|
407
|
-
if (isEmpty) cancelAcceptedTouchStream()
|
|
408
|
-
clipIsEmpty = isEmpty
|
|
409
|
-
visibility = clipVisibility(isEmpty)
|
|
410
|
-
importantForAccessibility = clipAccessibility(
|
|
411
|
-
isEmpty,
|
|
412
|
-
requestedImportantForAccessibility,
|
|
413
|
-
)
|
|
355
|
+
private fun adjustedRadiusForSpread(radius: Float, spread: Float): Float {
|
|
356
|
+
val magnitude = abs(spread)
|
|
357
|
+
val multiplier = if (magnitude > 0f && radius < magnitude) {
|
|
358
|
+
1f + (radius / magnitude - 1f).pow(3)
|
|
359
|
+
} else {
|
|
360
|
+
1f
|
|
414
361
|
}
|
|
362
|
+
return (radius + spread * multiplier).coerceAtLeast(0f)
|
|
363
|
+
}
|
|
415
364
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
365
|
+
private fun rebuildBoxShadowPath() {
|
|
366
|
+
if (!requestedShadowEnabled || requestedShadowAlpha <= 0f) return
|
|
367
|
+
val path = boxShadowPath ?: Path().also { boxShadowPath = it }
|
|
368
|
+
path.reset()
|
|
369
|
+
if (clipIsEmpty) return
|
|
370
|
+
val spread = requestedShadowSpreadDistance
|
|
371
|
+
val left = clipLeft - spread + requestedShadowOffsetX
|
|
372
|
+
val top = clipTop - spread + requestedShadowOffsetY
|
|
373
|
+
val right = clipRight + spread + requestedShadowOffsetX
|
|
374
|
+
val bottom = clipBottom + spread + requestedShadowOffsetY
|
|
375
|
+
if (right <= left || bottom <= top) return
|
|
376
|
+
appendRoundedRectPath(
|
|
377
|
+
path,
|
|
378
|
+
left,
|
|
379
|
+
top,
|
|
380
|
+
right,
|
|
381
|
+
bottom,
|
|
382
|
+
adjustedRadiusForSpread(clipTopLeftRadius, spread),
|
|
383
|
+
adjustedRadiusForSpread(clipTopRightRadius, spread),
|
|
384
|
+
adjustedRadiusForSpread(clipBottomRightRadius, spread),
|
|
385
|
+
adjustedRadiusForSpread(clipBottomLeftRadius, spread),
|
|
386
|
+
clipCurveCode,
|
|
387
|
+
)
|
|
388
|
+
}
|
|
419
389
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
390
|
+
override fun dispatchDraw(canvas: Canvas) {
|
|
391
|
+
val hostSaveCount = canvas.save()
|
|
392
|
+
canvas.clipRect(0f, 0f, width.toFloat(), height.toFloat())
|
|
393
|
+
try {
|
|
394
|
+
val shadowPath = boxShadowPath
|
|
395
|
+
val shadowPaint = boxShadowPaint
|
|
396
|
+
if (requestedShadowEnabled && !clipIsEmpty &&
|
|
397
|
+
requestedShadowAlpha > 0f && shadowPath != null &&
|
|
398
|
+
shadowPaint != null && !shadowPath.isEmpty
|
|
399
|
+
) {
|
|
400
|
+
val apertureSaveCount = canvas.save()
|
|
401
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
402
|
+
canvas.clipOutPath(clipPath)
|
|
403
|
+
} else {
|
|
404
|
+
@Suppress("DEPRECATION")
|
|
405
|
+
canvas.clipPath(clipPath, Region.Op.DIFFERENCE)
|
|
406
|
+
}
|
|
407
|
+
canvas.drawPath(shadowPath, shadowPaint)
|
|
408
|
+
canvas.restoreToCount(apertureSaveCount)
|
|
409
|
+
}
|
|
410
|
+
super.dispatchDraw(canvas)
|
|
411
|
+
} finally {
|
|
412
|
+
canvas.restoreToCount(hostSaveCount)
|
|
413
|
+
}
|
|
434
414
|
}
|
|
435
415
|
|
|
436
|
-
private fun
|
|
416
|
+
private fun applyCanonicalClipPx(
|
|
437
417
|
left: Float,
|
|
438
418
|
top: Float,
|
|
439
419
|
right: Float,
|
|
@@ -444,24 +424,9 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
444
424
|
bottomLeftRadius: Float,
|
|
445
425
|
curveCode: Int,
|
|
446
426
|
) {
|
|
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
427
|
val isEmpty = right <= left || bottom <= top
|
|
463
|
-
val
|
|
464
|
-
|
|
428
|
+
val geometryChanged = left != clipLeft || top != clipTop ||
|
|
429
|
+
right != clipRight ||
|
|
465
430
|
bottom != clipBottom || topLeftRadius != clipTopLeftRadius ||
|
|
466
431
|
topRightRadius != clipTopRightRadius ||
|
|
467
432
|
bottomRightRadius != clipBottomRightRadius ||
|
|
@@ -471,122 +436,56 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
471
436
|
clipTop = top
|
|
472
437
|
clipRight = right
|
|
473
438
|
clipBottom = bottom
|
|
474
|
-
|
|
439
|
+
val radiiAreUniform = topLeftRadius == topRightRadius &&
|
|
440
|
+
topLeftRadius == bottomRightRadius &&
|
|
441
|
+
topLeftRadius == bottomLeftRadius
|
|
475
442
|
clipTopLeftRadius = topLeftRadius
|
|
476
443
|
clipTopRightRadius = topRightRadius
|
|
477
444
|
clipBottomRightRadius = bottomRightRadius
|
|
478
445
|
clipBottomLeftRadius = bottomLeftRadius
|
|
479
446
|
clipCurveCode = curveCode
|
|
480
|
-
|
|
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
|
-
}
|
|
447
|
+
applyContentTransform()
|
|
517
448
|
|
|
518
|
-
|
|
519
|
-
|
|
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
|
|
449
|
+
clipIsEmpty = isEmpty
|
|
450
|
+
reapplyClipPresentation()
|
|
535
451
|
|
|
536
|
-
|
|
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
|
-
}
|
|
452
|
+
if (!geometryChanged) return
|
|
559
453
|
|
|
560
|
-
if (!outlineGeometryChanged) return
|
|
561
|
-
outlineUsesPath = false
|
|
562
|
-
outlineUsesFloatRoundRect = true
|
|
563
454
|
clipPath.reset()
|
|
564
455
|
if (!isEmpty) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
456
|
+
if (curveCode == CLIP_CURVE_CIRCULAR && radiiAreUniform) {
|
|
457
|
+
clipPath.addRoundRect(
|
|
458
|
+
left,
|
|
459
|
+
top,
|
|
460
|
+
right,
|
|
461
|
+
bottom,
|
|
462
|
+
topLeftRadius,
|
|
463
|
+
topLeftRadius,
|
|
464
|
+
Path.Direction.CW,
|
|
465
|
+
)
|
|
466
|
+
} else {
|
|
467
|
+
appendRoundedRectPath(
|
|
468
|
+
clipPath,
|
|
469
|
+
left,
|
|
470
|
+
top,
|
|
471
|
+
right,
|
|
472
|
+
bottom,
|
|
473
|
+
topLeftRadius,
|
|
474
|
+
topRightRadius,
|
|
475
|
+
bottomRightRadius,
|
|
476
|
+
bottomLeftRadius,
|
|
477
|
+
curveCode,
|
|
478
|
+
)
|
|
479
|
+
}
|
|
574
480
|
}
|
|
575
|
-
|
|
481
|
+
rebuildBoxShadowPath()
|
|
482
|
+
clipContainer.invalidateOutline()
|
|
483
|
+
if (requestedShadowEnabled) invalidate()
|
|
576
484
|
}
|
|
577
485
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
* so only the clip edge moves and the content stays exactly where the
|
|
582
|
-
* driver put it. Every write self-dedupes inside View.setTranslationX, so
|
|
583
|
-
* calling this on an unchanged frame costs four float comparisons.
|
|
584
|
-
*/
|
|
585
|
-
private fun applyClipPlacement() {
|
|
586
|
-
super.setTranslationX(userTranslationX + clipResidualX)
|
|
587
|
-
super.setTranslationY(userTranslationY + clipResidualY)
|
|
588
|
-
contentContainer.translationX = requestedContentTranslateX - clipResidualX
|
|
589
|
-
contentContainer.translationY = requestedContentTranslateY - clipResidualY
|
|
486
|
+
private fun applyContentTransform() {
|
|
487
|
+
contentContainer.translationX = requestedContentTranslateX
|
|
488
|
+
contentContainer.translationY = requestedContentTranslateY
|
|
590
489
|
// Scale is intentionally centered and lives on the content only. View
|
|
591
490
|
// translation properties are applied independently of scale, so a
|
|
592
491
|
// caller's tx/ty remains a physical-pixel offset rather than scaling
|
|
@@ -597,36 +496,14 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
597
496
|
contentContainer.scaleY = requestedContentScale
|
|
598
497
|
}
|
|
599
498
|
|
|
600
|
-
// The clip's sub-pixel placement and the consumer's `transform` prop share
|
|
601
|
-
// one property, so they are composed rather than allowed to overwrite each
|
|
602
|
-
// other. RN routes every transform write through these setters
|
|
603
|
-
// (BaseViewManager.setTransformProperty), so intercepting them is enough.
|
|
604
|
-
//
|
|
605
|
-
// The getters are deliberately NOT overridden to hide the residual:
|
|
606
|
-
// View.setTranslationX dedupes by calling getTranslationX() virtually, so
|
|
607
|
-
// reporting the consumer's value there would make every write look like a
|
|
608
|
-
// change and re-invalidate the view on frames where nothing moved. The cost
|
|
609
|
-
// of leaving them alone is that a read-modify-write of translationX picks up
|
|
610
|
-
// the residual twice — sub-pixel, and nothing in RN's transform path reads
|
|
611
|
-
// back before writing.
|
|
612
|
-
override fun setTranslationX(translationX: Float) {
|
|
613
|
-
userTranslationX = translationX
|
|
614
|
-
super.setTranslationX(translationX + clipResidualX)
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
override fun setTranslationY(translationY: Float) {
|
|
618
|
-
userTranslationY = translationY
|
|
619
|
-
super.setTranslationY(translationY + clipResidualY)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
499
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
623
500
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
501
|
+
clipContainer.layout(0, 0, w, h)
|
|
624
502
|
contentContainer.layout(0, 0, w, h)
|
|
625
|
-
|
|
503
|
+
applyContentTransform()
|
|
626
504
|
if (boundDriverId != 0.0) {
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
// visible value synchronously.
|
|
505
|
+
// Host metrics only gate lifecycle readiness. Redelivery remains
|
|
506
|
+
// raw and canonical; this view owns the final viewport crop.
|
|
630
507
|
SmoothClipBindings.nativeSetViewHostGeometry(
|
|
631
508
|
boundDriverId,
|
|
632
509
|
this,
|
|
@@ -641,6 +518,7 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
641
518
|
// authoritative command wins over the driver value on resize.
|
|
642
519
|
applyRequestedGeometry()
|
|
643
520
|
}
|
|
521
|
+
reapplyClipPresentation()
|
|
644
522
|
}
|
|
645
523
|
|
|
646
524
|
override fun onAttachedToWindow() {
|
|
@@ -695,89 +573,33 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
695
573
|
}
|
|
696
574
|
|
|
697
575
|
private fun containsRoundedPoint(x: Float, y: Float): Boolean {
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
// so hit testing stays against the geometry the driver delivered — the
|
|
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
|
-
}
|
|
709
|
-
return containsRoundedPointPx(
|
|
710
|
-
x + clipResidualX,
|
|
711
|
-
y + clipResidualY,
|
|
712
|
-
clipLeft,
|
|
713
|
-
clipTop,
|
|
714
|
-
clipRight,
|
|
715
|
-
clipBottom,
|
|
716
|
-
clipRadius,
|
|
717
|
-
clipIsEmpty,
|
|
718
|
-
)
|
|
576
|
+
// Reuse the exact path supplied to the Outline. Hit testing therefore
|
|
577
|
+
// follows the rendered aperture, including continuous corners.
|
|
578
|
+
return !clipIsEmpty && containsPathPoint(clipPath, x, y)
|
|
719
579
|
}
|
|
720
580
|
|
|
721
581
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
retainTouchEvent(event)
|
|
730
|
-
} else {
|
|
731
|
-
clearTouchState()
|
|
732
|
-
}
|
|
733
|
-
return accepted
|
|
734
|
-
}
|
|
735
|
-
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
|
|
736
|
-
val result = if (acceptingTouchStream) {
|
|
737
|
-
super.dispatchTouchEvent(event)
|
|
738
|
-
} else {
|
|
739
|
-
false
|
|
740
|
-
}
|
|
741
|
-
clearTouchState()
|
|
742
|
-
return result
|
|
743
|
-
}
|
|
582
|
+
val action = event.actionMasked
|
|
583
|
+
if (action == MotionEvent.ACTION_DOWN) {
|
|
584
|
+
// The aperture gates admission only. Once accepted, the stream
|
|
585
|
+
// belongs to Android until its real UP or CANCEL arrives.
|
|
586
|
+
clearTouchState()
|
|
587
|
+
if (!containsRoundedPoint(event.x, event.y)) return false
|
|
588
|
+
acceptingTouchStream = true
|
|
744
589
|
}
|
|
745
590
|
|
|
746
591
|
if (!acceptingTouchStream) return false
|
|
747
592
|
val result = super.dispatchTouchEvent(event)
|
|
748
|
-
|
|
593
|
+
if ((action == MotionEvent.ACTION_DOWN && !result) ||
|
|
594
|
+
action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP
|
|
595
|
+
) {
|
|
596
|
+
clearTouchState()
|
|
597
|
+
}
|
|
749
598
|
return result
|
|
750
599
|
}
|
|
751
600
|
|
|
752
|
-
private fun retainTouchEvent(event: MotionEvent) {
|
|
753
|
-
retainedTouchEvent?.recycle()
|
|
754
|
-
retainedTouchEvent = MotionEvent.obtain(event)
|
|
755
|
-
}
|
|
756
|
-
|
|
757
601
|
private fun clearTouchState() {
|
|
758
602
|
acceptingTouchStream = false
|
|
759
|
-
retainedTouchEvent?.recycle()
|
|
760
|
-
retainedTouchEvent = null
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
private fun cancelAcceptedTouchStream() {
|
|
764
|
-
val latest = retainedTouchEvent
|
|
765
|
-
if (!acceptingTouchStream || latest == null) {
|
|
766
|
-
clearTouchState()
|
|
767
|
-
return
|
|
768
|
-
}
|
|
769
|
-
val cancel = MotionEvent.obtain(latest)
|
|
770
|
-
cancel.action = MotionEvent.ACTION_CANCEL
|
|
771
|
-
// Terminal BEFORE dispatch: a child's synchronous cancel handler can
|
|
772
|
-
// re-enter geometry application, and a nested emptiness transition
|
|
773
|
-
// must observe an already-ended stream instead of synthesizing a
|
|
774
|
-
// second cancel for it.
|
|
775
|
-
clearTouchState()
|
|
776
|
-
try {
|
|
777
|
-
super.dispatchTouchEvent(cancel)
|
|
778
|
-
} finally {
|
|
779
|
-
cancel.recycle()
|
|
780
|
-
}
|
|
781
603
|
}
|
|
782
604
|
|
|
783
605
|
fun setRequestedImportantForAccessibility(value: Int) {
|
|
@@ -785,14 +607,24 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
785
607
|
reapplyClipPresentation()
|
|
786
608
|
}
|
|
787
609
|
|
|
610
|
+
@DoNotStrip
|
|
611
|
+
fun setAutonomousMotion(active: Boolean) {
|
|
612
|
+
if (autonomousMotion == active) return
|
|
613
|
+
autonomousMotion = active
|
|
614
|
+
reapplyClipPresentation()
|
|
615
|
+
}
|
|
616
|
+
|
|
788
617
|
fun reapplyClipPresentation() {
|
|
789
|
-
val
|
|
618
|
+
val apertureVisible = apertureIntersectsHost()
|
|
619
|
+
val expectedVisibility = renderVisibility(
|
|
620
|
+
apertureVisible || shadowIntersectsHost(),
|
|
621
|
+
)
|
|
790
622
|
if (visibility != expectedVisibility) {
|
|
791
623
|
visibility = expectedVisibility
|
|
792
624
|
}
|
|
793
625
|
|
|
794
626
|
val expectedAccessibility = clipAccessibility(
|
|
795
|
-
|
|
627
|
+
autonomousMotion || !apertureVisible,
|
|
796
628
|
requestedImportantForAccessibility,
|
|
797
629
|
)
|
|
798
630
|
if (importantForAccessibility != expectedAccessibility) {
|
|
@@ -800,6 +632,29 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
800
632
|
}
|
|
801
633
|
}
|
|
802
634
|
|
|
635
|
+
private fun apertureIntersectsHost(): Boolean =
|
|
636
|
+
!clipIsEmpty && clipRight > 0f && clipBottom > 0f &&
|
|
637
|
+
clipLeft < width.toFloat() && clipTop < height.toFloat()
|
|
638
|
+
|
|
639
|
+
private fun shadowIntersectsHost(): Boolean {
|
|
640
|
+
if (clipIsEmpty || !requestedShadowEnabled || requestedShadowAlpha <= 0f) {
|
|
641
|
+
return false
|
|
642
|
+
}
|
|
643
|
+
val spread = requestedShadowSpreadDistance
|
|
644
|
+
val pathLeft = clipLeft - spread + requestedShadowOffsetX
|
|
645
|
+
val pathTop = clipTop - spread + requestedShadowOffsetY
|
|
646
|
+
val pathRight = clipRight + spread + requestedShadowOffsetX
|
|
647
|
+
val pathBottom = clipBottom + spread + requestedShadowOffsetY
|
|
648
|
+
if (pathRight <= pathLeft || pathBottom <= pathTop) return false
|
|
649
|
+
|
|
650
|
+
// CSS blur is specified as a diameter-like radius. Expanding by the
|
|
651
|
+
// full value is conservative and prevents culling a faint blur tail.
|
|
652
|
+
val blurOutset = requestedShadowBlurRadius
|
|
653
|
+
return pathRight + blurOutset > 0f && pathBottom + blurOutset > 0f &&
|
|
654
|
+
pathLeft - blurOutset < width.toFloat() &&
|
|
655
|
+
pathTop - blurOutset < height.toFloat()
|
|
656
|
+
}
|
|
657
|
+
|
|
803
658
|
fun resetClipState() {
|
|
804
659
|
clearTouchState()
|
|
805
660
|
commandIsAuthoritative = false
|
|
@@ -807,7 +662,6 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
807
662
|
requestedY = 0f
|
|
808
663
|
requestedWidth = 0f
|
|
809
664
|
requestedHeight = 0f
|
|
810
|
-
requestedRadius = 0f
|
|
811
665
|
requestedTopLeftRadius = 0f
|
|
812
666
|
requestedTopRightRadius = 0f
|
|
813
667
|
requestedBottomRightRadius = 0f
|
|
@@ -816,17 +670,19 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
816
670
|
requestedContentTranslateX = 0f
|
|
817
671
|
requestedContentTranslateY = 0f
|
|
818
672
|
requestedContentScale = 1f
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
673
|
+
requestedShadowEnabled = false
|
|
674
|
+
requestedShadowRed = 0f
|
|
675
|
+
requestedShadowGreen = 0f
|
|
676
|
+
requestedShadowBlue = 0f
|
|
677
|
+
requestedShadowAlpha = 1f
|
|
678
|
+
requestedShadowOffsetX = 0f
|
|
679
|
+
requestedShadowOffsetY = 0f
|
|
680
|
+
requestedShadowBlurRadius = 0f
|
|
681
|
+
requestedShadowSpreadDistance = 0f
|
|
682
|
+
boxShadowPaint = null
|
|
683
|
+
boxShadowPath = null
|
|
684
|
+
applyContentTransform()
|
|
825
685
|
clipPath.reset()
|
|
826
|
-
outlineLeft = 0
|
|
827
|
-
outlineTop = 0
|
|
828
|
-
outlineRight = 0
|
|
829
|
-
outlineBottom = 0
|
|
830
686
|
requestedImportantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
831
687
|
applyRequestedGeometry()
|
|
832
688
|
}
|