react-native-smooth-clip-view 0.2.9 → 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 -349
- package/android/build.gradle +8 -4
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +13 -6
- package/android/src/main/cpp/SmoothClipBindings.cpp +605 -123
- package/android/src/main/cpp/SmoothClipRegistry.cpp +975 -209
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +192 -118
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +22 -4
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +81 -56
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +449 -240
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +150 -38
- package/cpp/SmoothClipAnimationCurve.h +317 -397
- package/cpp/SmoothClipRegistry.h +84 -20
- package/cpp/SmoothClipSharedGeometry.h +131 -22
- package/cpp/SmoothClipVelocityTracker.h +16 -12
- package/ios/SmoothClipGeometry.h +188 -21
- package/ios/SmoothClipRegistry.mm +869 -368
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +513 -156
- package/ios/SmoothClipTurboModule.h +123 -56
- package/ios/SmoothClipView.mm +1074 -449
- package/ios/SmoothClipViewRegistry.h +3 -7
- 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 +94 -11
- 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 +32 -12
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +18 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +2 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- 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 +180 -30
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/groupTypes.js.map +1 -0
- 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 +4 -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 +20 -4
- 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 +139 -4
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +16 -3
- 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 +4 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- 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 +49 -5
- 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 +8 -5
- 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 +17 -13
- package/src/NativeSmoothClipModule.ts +80 -56
- package/src/SmoothClipView.ios.tsx +142 -16
- package/src/SmoothClipView.tsx +6 -76
- package/src/SmoothClipViewNativeComponent.ts +32 -12
- package/src/capabilities.native.ts +19 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +3 -0
- 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 +293 -45
- 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 +32 -12
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/driverState.js +0 -110
- 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 -284
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -556
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.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/driverState.d.ts +0 -31
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -107
- 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 -5
- 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/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.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/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/driverState.ts +0 -158
- package/src/driverTypes.ts +0 -140
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -899
- package/src/drivers.ts +0 -427
- package/src/index.web.ts +0 -23
- package/src/reactRequests.ts +0 -76
- package/src/webClipPath.ts +0 -30
|
@@ -1,7 +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
|
|
9
|
+
import android.graphics.Path
|
|
10
|
+
import android.graphics.Region
|
|
11
|
+
import android.os.Build
|
|
5
12
|
import android.os.Trace
|
|
6
13
|
import android.view.MotionEvent
|
|
7
14
|
import android.view.View
|
|
@@ -10,44 +17,55 @@ import com.facebook.proguard.annotations.DoNotStrip
|
|
|
10
17
|
import com.facebook.react.uimanager.PixelUtil
|
|
11
18
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
12
19
|
import com.facebook.react.views.view.ReactViewGroup
|
|
20
|
+
import kotlin.math.roundToInt
|
|
21
|
+
import kotlin.math.abs
|
|
22
|
+
import kotlin.math.pow
|
|
13
23
|
|
|
14
24
|
class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
25
|
+
private val clipContainer = ReactViewGroup(context)
|
|
15
26
|
internal val contentContainer = ReactViewGroup(context)
|
|
16
27
|
private var requestedX = 0f
|
|
17
28
|
private var requestedY = 0f
|
|
18
29
|
private var requestedWidth = 0f
|
|
19
30
|
private var requestedHeight = 0f
|
|
20
|
-
private var
|
|
31
|
+
private var requestedTopLeftRadius = 0f
|
|
32
|
+
private var requestedTopRightRadius = 0f
|
|
33
|
+
private var requestedBottomRightRadius = 0f
|
|
34
|
+
private var requestedBottomLeftRadius = 0f
|
|
35
|
+
private var requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
21
36
|
private var requestedContentTranslateX = 0f
|
|
22
37
|
private var requestedContentTranslateY = 0f
|
|
38
|
+
private var requestedContentScale = 1f
|
|
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
|
|
23
48
|
private var clipLeft = 0f
|
|
24
49
|
private var clipTop = 0f
|
|
25
50
|
private var clipRight = 0f
|
|
26
51
|
private var clipBottom = 0f
|
|
27
|
-
private var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
private var
|
|
31
|
-
private var
|
|
32
|
-
private
|
|
33
|
-
private var
|
|
52
|
+
private var clipTopLeftRadius = 0f
|
|
53
|
+
private var clipTopRightRadius = 0f
|
|
54
|
+
private var clipBottomRightRadius = 0f
|
|
55
|
+
private var clipBottomLeftRadius = 0f
|
|
56
|
+
private var clipCurveCode = CLIP_CURVE_CIRCULAR
|
|
57
|
+
private val clipPath = Path()
|
|
58
|
+
private var boxShadowPath: Path? = null
|
|
59
|
+
private var boxShadowPaint: Paint? = null
|
|
34
60
|
private var clipIsEmpty = true
|
|
35
|
-
// Sub-pixel remainder of the clip origin, carried on this view's own
|
|
36
|
-
// translation and subtracted back out of the content container.
|
|
37
|
-
private var clipResidualX = 0f
|
|
38
|
-
private var clipResidualY = 0f
|
|
39
|
-
// The consumer's `transform` translation, kept apart from the residual so
|
|
40
|
-
// the two compose instead of clobbering each other.
|
|
41
|
-
private var userTranslationX = 0f
|
|
42
|
-
private var userTranslationY = 0f
|
|
43
61
|
private var requestedImportantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
62
|
+
private var autonomousMotion = false
|
|
44
63
|
private var acceptingTouchStream = false
|
|
45
|
-
private var retainedTouchEvent: MotionEvent? = null
|
|
46
64
|
|
|
47
65
|
/** Driver this view is registered with in the native registry (0 = none). */
|
|
48
66
|
internal var boundDriverId: Double = 0.0
|
|
49
67
|
|
|
50
|
-
/** Set once a
|
|
68
|
+
/** Set once a command arrives; later initial presentation props are ignored. */
|
|
51
69
|
internal var commandIsAuthoritative = false
|
|
52
70
|
|
|
53
71
|
private val clipOutlineProvider = object : ViewOutlineProvider() {
|
|
@@ -57,45 +75,62 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
57
75
|
return
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
outlineRight,
|
|
64
|
-
outlineBottom,
|
|
65
|
-
clipRadius,
|
|
66
|
-
)
|
|
78
|
+
// Outline.setRoundRect only accepts integer edges. The raw path
|
|
79
|
+
// keeps sub-pixel coordinates and off-host geometry intact.
|
|
80
|
+
outline.setPath(clipPath)
|
|
67
81
|
}
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
init {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
clipContainer.addView(contentContainer)
|
|
86
|
+
super.addView(clipContainer)
|
|
87
|
+
clipContainer.outlineProvider = clipOutlineProvider
|
|
88
|
+
clipContainer.clipToOutline = true
|
|
89
|
+
clipToOutline = false
|
|
90
|
+
setWillNotDraw(false)
|
|
74
91
|
visibility = INVISIBLE
|
|
75
92
|
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
x: Double,
|
|
80
|
-
y: Double,
|
|
81
|
-
width: Double,
|
|
82
|
-
height: Double,
|
|
83
|
-
radius: Double,
|
|
84
|
-
) = setClipPresentationDip(x, y, width, height, radius, 0.0, 0.0)
|
|
85
|
-
|
|
95
|
+
/** DIP fallback used until a registered view has pushed host metrics. */
|
|
86
96
|
@DoNotStrip
|
|
87
97
|
fun setClipPresentationDip(
|
|
88
98
|
x: Double,
|
|
89
99
|
y: Double,
|
|
90
100
|
width: Double,
|
|
91
101
|
height: Double,
|
|
92
|
-
|
|
102
|
+
topLeftRadius: Double,
|
|
103
|
+
topRightRadius: Double,
|
|
104
|
+
bottomRightRadius: Double,
|
|
105
|
+
bottomLeftRadius: Double,
|
|
106
|
+
curveCode: Int,
|
|
93
107
|
contentTranslateX: Double,
|
|
94
108
|
contentTranslateY: Double,
|
|
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,
|
|
95
119
|
) {
|
|
96
120
|
if (!x.isFinite() || !y.isFinite() || !width.isFinite() ||
|
|
97
|
-
!height.isFinite() || !
|
|
98
|
-
!
|
|
121
|
+
!height.isFinite() || !topLeftRadius.isFinite() ||
|
|
122
|
+
!topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
|
|
123
|
+
!bottomLeftRadius.isFinite() || !contentTranslateX.isFinite() ||
|
|
124
|
+
!contentTranslateY.isFinite() || !contentScale.isFinite() ||
|
|
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 ||
|
|
133
|
+
(curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
|
|
99
134
|
) {
|
|
100
135
|
return
|
|
101
136
|
}
|
|
@@ -104,12 +139,26 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
104
139
|
val nextY = PixelUtil.toPixelFromDIP(y).toFloat()
|
|
105
140
|
val nextWidth = PixelUtil.toPixelFromDIP(width).toFloat()
|
|
106
141
|
val nextHeight = PixelUtil.toPixelFromDIP(height).toFloat()
|
|
107
|
-
val
|
|
142
|
+
val nextTopLeftRadius = PixelUtil.toPixelFromDIP(topLeftRadius).toFloat()
|
|
143
|
+
val nextTopRightRadius = PixelUtil.toPixelFromDIP(topRightRadius).toFloat()
|
|
144
|
+
val nextBottomRightRadius = PixelUtil.toPixelFromDIP(bottomRightRadius).toFloat()
|
|
145
|
+
val nextBottomLeftRadius = PixelUtil.toPixelFromDIP(bottomLeftRadius).toFloat()
|
|
108
146
|
val nextContentTranslateX = PixelUtil.toPixelFromDIP(contentTranslateX).toFloat()
|
|
109
147
|
val nextContentTranslateY = PixelUtil.toPixelFromDIP(contentTranslateY).toFloat()
|
|
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()
|
|
110
154
|
if (!nextX.isFinite() || !nextY.isFinite() || !nextWidth.isFinite() ||
|
|
111
|
-
!nextHeight.isFinite() || !
|
|
112
|
-
!
|
|
155
|
+
!nextHeight.isFinite() || !nextTopLeftRadius.isFinite() ||
|
|
156
|
+
!nextTopRightRadius.isFinite() || !nextBottomRightRadius.isFinite() ||
|
|
157
|
+
!nextBottomLeftRadius.isFinite() || !nextContentTranslateX.isFinite() ||
|
|
158
|
+
!nextContentTranslateY.isFinite() || !nextContentScale.isFinite() ||
|
|
159
|
+
!nextShadowOffsetX.isFinite() || !nextShadowOffsetY.isFinite() ||
|
|
160
|
+
!nextShadowBlurRadius.isFinite() ||
|
|
161
|
+
!nextShadowSpreadDistance.isFinite() || nextContentScale <= 0f
|
|
113
162
|
) {
|
|
114
163
|
return
|
|
115
164
|
}
|
|
@@ -118,178 +167,343 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
118
167
|
requestedY = nextY
|
|
119
168
|
requestedWidth = nextWidth
|
|
120
169
|
requestedHeight = nextHeight
|
|
121
|
-
|
|
170
|
+
requestedTopLeftRadius = nextTopLeftRadius
|
|
171
|
+
requestedTopRightRadius = nextTopRightRadius
|
|
172
|
+
requestedBottomRightRadius = nextBottomRightRadius
|
|
173
|
+
requestedBottomLeftRadius = nextBottomLeftRadius
|
|
174
|
+
requestedCurveCode = curveCode
|
|
122
175
|
requestedContentTranslateX = nextContentTranslateX
|
|
123
176
|
requestedContentTranslateY = nextContentTranslateY
|
|
177
|
+
requestedContentScale = nextContentScale
|
|
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
|
+
)
|
|
124
189
|
applyRequestedGeometry()
|
|
125
190
|
}
|
|
126
191
|
|
|
127
|
-
/**
|
|
128
|
-
* Driver hot path: called from the C++ registry with final pixel-space,
|
|
129
|
-
* pre-normalized values (see deliverToView in SmoothClipRegistry.cpp).
|
|
130
|
-
* No validation, scaling or normalization happens here.
|
|
131
|
-
*/
|
|
192
|
+
/** Driver hot path. Geometry is canonical, raw, and host-independent. */
|
|
132
193
|
@DoNotStrip
|
|
133
194
|
fun setClipPresentationPx(
|
|
134
195
|
left: Float,
|
|
135
196
|
top: Float,
|
|
136
197
|
right: Float,
|
|
137
198
|
bottom: Float,
|
|
138
|
-
|
|
199
|
+
topLeftRadius: Float,
|
|
200
|
+
topRightRadius: Float,
|
|
201
|
+
bottomRightRadius: Float,
|
|
202
|
+
bottomLeftRadius: Float,
|
|
203
|
+
curveCode: Int,
|
|
139
204
|
contentTranslateX: Float,
|
|
140
205
|
contentTranslateY: Float,
|
|
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,
|
|
141
216
|
) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
217
|
+
if (!left.isFinite() || !top.isFinite() || !right.isFinite() ||
|
|
218
|
+
!bottom.isFinite() || !topLeftRadius.isFinite() ||
|
|
219
|
+
!topRightRadius.isFinite() || !bottomRightRadius.isFinite() ||
|
|
220
|
+
!bottomLeftRadius.isFinite() || !contentTranslateX.isFinite() ||
|
|
221
|
+
!contentTranslateY.isFinite() || !contentScale.isFinite() ||
|
|
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 ||
|
|
230
|
+
(curveCode != CLIP_CURVE_CIRCULAR && curveCode != CLIP_CURVE_CONTINUOUS)
|
|
231
|
+
) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (BuildConfig.DEBUG) Trace.beginSection("SmoothClip.applyPresentationPx")
|
|
145
236
|
try {
|
|
146
|
-
// Stored only; applyNormalizedClipPx computes the clip residual and
|
|
147
|
-
// applyClipPlacement writes both translations together, because the
|
|
148
|
-
// content's final offset depends on the residual as well.
|
|
149
237
|
requestedContentTranslateX = contentTranslateX
|
|
150
238
|
requestedContentTranslateY = contentTranslateY
|
|
151
|
-
|
|
239
|
+
requestedContentScale = contentScale
|
|
240
|
+
storeShadow(
|
|
241
|
+
shadowEnabled,
|
|
242
|
+
shadowRed,
|
|
243
|
+
shadowGreen,
|
|
244
|
+
shadowBlue,
|
|
245
|
+
shadowAlpha,
|
|
246
|
+
shadowOffsetX,
|
|
247
|
+
shadowOffsetY,
|
|
248
|
+
shadowBlurRadius,
|
|
249
|
+
shadowSpreadDistance,
|
|
250
|
+
)
|
|
251
|
+
applyCanonicalClipPx(
|
|
252
|
+
left,
|
|
253
|
+
top,
|
|
254
|
+
right,
|
|
255
|
+
bottom,
|
|
256
|
+
topLeftRadius,
|
|
257
|
+
topRightRadius,
|
|
258
|
+
bottomRightRadius,
|
|
259
|
+
bottomLeftRadius,
|
|
260
|
+
curveCode,
|
|
261
|
+
)
|
|
152
262
|
} finally {
|
|
153
263
|
if (BuildConfig.DEBUG) Trace.endSection()
|
|
154
264
|
}
|
|
155
265
|
}
|
|
156
266
|
|
|
157
267
|
private fun applyRequestedGeometry() {
|
|
158
|
-
|
|
268
|
+
canonicalizeClipGeometryPx(
|
|
159
269
|
requestedX,
|
|
160
270
|
requestedY,
|
|
161
271
|
requestedWidth,
|
|
162
272
|
requestedHeight,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
+
)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
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,
|
|
303
|
+
) {
|
|
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(),
|
|
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
|
|
168
352
|
}
|
|
169
353
|
}
|
|
170
354
|
|
|
171
|
-
private fun
|
|
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
|
|
361
|
+
}
|
|
362
|
+
return (radius + spread * multiplier).coerceAtLeast(0f)
|
|
363
|
+
}
|
|
364
|
+
|
|
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
|
+
}
|
|
389
|
+
|
|
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
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private fun applyCanonicalClipPx(
|
|
172
417
|
left: Float,
|
|
173
418
|
top: Float,
|
|
174
419
|
right: Float,
|
|
175
420
|
bottom: Float,
|
|
176
|
-
|
|
421
|
+
topLeftRadius: Float,
|
|
422
|
+
topRightRadius: Float,
|
|
423
|
+
bottomRightRadius: Float,
|
|
424
|
+
bottomLeftRadius: Float,
|
|
425
|
+
curveCode: Int,
|
|
177
426
|
) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
val nextOutlineBottom = outlineFarEdge(top, bottom)
|
|
186
|
-
// Outline.setRoundRect collapses a degenerate integer rect to empty.
|
|
187
|
-
// Use the exact emitted geometry as the single semantic source for
|
|
188
|
-
// rendering, visibility, accessibility and hit testing.
|
|
189
|
-
val isEmpty = outlineRectIsEmpty(
|
|
190
|
-
nextOutlineLeft,
|
|
191
|
-
nextOutlineTop,
|
|
192
|
-
nextOutlineRight,
|
|
193
|
-
nextOutlineBottom,
|
|
194
|
-
)
|
|
195
|
-
val outlineGeometryChanged = outlineChanged(
|
|
196
|
-
nextOutlineLeft,
|
|
197
|
-
nextOutlineTop,
|
|
198
|
-
nextOutlineRight,
|
|
199
|
-
nextOutlineBottom,
|
|
200
|
-
radius,
|
|
201
|
-
outlineLeft,
|
|
202
|
-
outlineTop,
|
|
203
|
-
outlineRight,
|
|
204
|
-
outlineBottom,
|
|
205
|
-
clipRadius,
|
|
206
|
-
)
|
|
427
|
+
val isEmpty = right <= left || bottom <= top
|
|
428
|
+
val geometryChanged = left != clipLeft || top != clipTop ||
|
|
429
|
+
right != clipRight ||
|
|
430
|
+
bottom != clipBottom || topLeftRadius != clipTopLeftRadius ||
|
|
431
|
+
topRightRadius != clipTopRightRadius ||
|
|
432
|
+
bottomRightRadius != clipBottomRightRadius ||
|
|
433
|
+
bottomLeftRadius != clipBottomLeftRadius || curveCode != clipCurveCode
|
|
207
434
|
|
|
208
435
|
clipLeft = left
|
|
209
436
|
clipTop = top
|
|
210
437
|
clipRight = right
|
|
211
438
|
clipBottom = bottom
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (isEmpty) cancelAcceptedTouchStream()
|
|
225
|
-
clipIsEmpty = isEmpty
|
|
226
|
-
visibility = clipVisibility(isEmpty)
|
|
227
|
-
importantForAccessibility = clipAccessibility(
|
|
228
|
-
isEmpty,
|
|
229
|
-
requestedImportantForAccessibility,
|
|
230
|
-
)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// An emptiness transition necessarily changes at least one emitted
|
|
234
|
-
// edge, so the existing edge/radius key cannot skip invalidateOutline.
|
|
235
|
-
if (!outlineGeometryChanged) return
|
|
236
|
-
|
|
237
|
-
outlineLeft = nextOutlineLeft
|
|
238
|
-
outlineTop = nextOutlineTop
|
|
239
|
-
outlineRight = nextOutlineRight
|
|
240
|
-
outlineBottom = nextOutlineBottom
|
|
241
|
-
// Schedules the traversal too: invalidateOutline() ends in
|
|
242
|
-
// invalidateViewProperty(), which damages this view in its parent up
|
|
243
|
-
// to ViewRootImpl.scheduleTraversals(). A plain invalidate() on top
|
|
244
|
-
// would only add PFLAG_INVALIDATED, forcing a display-list re-record
|
|
245
|
-
// every frame to restage an outline the RenderNode applies as a
|
|
246
|
-
// property.
|
|
247
|
-
invalidateOutline()
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Single writer for both translations. The view carries the sub-pixel
|
|
252
|
-
* remainder of the clip origin; the content container subtracts it back out
|
|
253
|
-
* so only the clip edge moves and the content stays exactly where the
|
|
254
|
-
* driver put it. Every write self-dedupes inside View.setTranslationX, so
|
|
255
|
-
* calling this on an unchanged frame costs four float comparisons.
|
|
256
|
-
*/
|
|
257
|
-
private fun applyClipPlacement() {
|
|
258
|
-
super.setTranslationX(userTranslationX + clipResidualX)
|
|
259
|
-
super.setTranslationY(userTranslationY + clipResidualY)
|
|
260
|
-
contentContainer.translationX = requestedContentTranslateX - clipResidualX
|
|
261
|
-
contentContainer.translationY = requestedContentTranslateY - clipResidualY
|
|
262
|
-
}
|
|
439
|
+
val radiiAreUniform = topLeftRadius == topRightRadius &&
|
|
440
|
+
topLeftRadius == bottomRightRadius &&
|
|
441
|
+
topLeftRadius == bottomLeftRadius
|
|
442
|
+
clipTopLeftRadius = topLeftRadius
|
|
443
|
+
clipTopRightRadius = topRightRadius
|
|
444
|
+
clipBottomRightRadius = bottomRightRadius
|
|
445
|
+
clipBottomLeftRadius = bottomLeftRadius
|
|
446
|
+
clipCurveCode = curveCode
|
|
447
|
+
applyContentTransform()
|
|
448
|
+
|
|
449
|
+
clipIsEmpty = isEmpty
|
|
450
|
+
reapplyClipPresentation()
|
|
263
451
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
452
|
+
if (!geometryChanged) return
|
|
453
|
+
|
|
454
|
+
clipPath.reset()
|
|
455
|
+
if (!isEmpty) {
|
|
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
|
+
}
|
|
480
|
+
}
|
|
481
|
+
rebuildBoxShadowPath()
|
|
482
|
+
clipContainer.invalidateOutline()
|
|
483
|
+
if (requestedShadowEnabled) invalidate()
|
|
279
484
|
}
|
|
280
485
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
486
|
+
private fun applyContentTransform() {
|
|
487
|
+
contentContainer.translationX = requestedContentTranslateX
|
|
488
|
+
contentContainer.translationY = requestedContentTranslateY
|
|
489
|
+
// Scale is intentionally centered and lives on the content only. View
|
|
490
|
+
// translation properties are applied independently of scale, so a
|
|
491
|
+
// caller's tx/ty remains a physical-pixel offset rather than scaling
|
|
492
|
+
// around the origin with the content.
|
|
493
|
+
contentContainer.pivotX = contentContainer.width / 2f
|
|
494
|
+
contentContainer.pivotY = contentContainer.height / 2f
|
|
495
|
+
contentContainer.scaleX = requestedContentScale
|
|
496
|
+
contentContainer.scaleY = requestedContentScale
|
|
284
497
|
}
|
|
285
498
|
|
|
286
499
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
287
500
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
501
|
+
clipContainer.layout(0, 0, w, h)
|
|
288
502
|
contentContainer.layout(0, 0, w, h)
|
|
503
|
+
applyContentTransform()
|
|
289
504
|
if (boundDriverId != 0.0) {
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
// visible value synchronously.
|
|
505
|
+
// Host metrics only gate lifecycle readiness. Redelivery remains
|
|
506
|
+
// raw and canonical; this view owns the final viewport crop.
|
|
293
507
|
SmoothClipBindings.nativeSetViewHostGeometry(
|
|
294
508
|
boundDriverId,
|
|
295
509
|
this,
|
|
@@ -304,6 +518,7 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
304
518
|
// authoritative command wins over the driver value on resize.
|
|
305
519
|
applyRequestedGeometry()
|
|
306
520
|
}
|
|
521
|
+
reapplyClipPresentation()
|
|
307
522
|
}
|
|
308
523
|
|
|
309
524
|
override fun onAttachedToWindow() {
|
|
@@ -358,83 +573,33 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
358
573
|
}
|
|
359
574
|
|
|
360
575
|
private fun containsRoundedPoint(x: Float, y: Float): Boolean {
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
// so hit testing stays against the geometry the driver delivered — the
|
|
365
|
-
// same test as before the residual existed.
|
|
366
|
-
return containsRoundedPointPx(
|
|
367
|
-
x + clipResidualX,
|
|
368
|
-
y + clipResidualY,
|
|
369
|
-
clipLeft,
|
|
370
|
-
clipTop,
|
|
371
|
-
clipRight,
|
|
372
|
-
clipBottom,
|
|
373
|
-
clipRadius,
|
|
374
|
-
clipIsEmpty,
|
|
375
|
-
)
|
|
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)
|
|
376
579
|
}
|
|
377
580
|
|
|
378
581
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
retainTouchEvent(event)
|
|
387
|
-
} else {
|
|
388
|
-
clearTouchState()
|
|
389
|
-
}
|
|
390
|
-
return accepted
|
|
391
|
-
}
|
|
392
|
-
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
|
|
393
|
-
val result = if (acceptingTouchStream) {
|
|
394
|
-
super.dispatchTouchEvent(event)
|
|
395
|
-
} else {
|
|
396
|
-
false
|
|
397
|
-
}
|
|
398
|
-
clearTouchState()
|
|
399
|
-
return result
|
|
400
|
-
}
|
|
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
|
|
401
589
|
}
|
|
402
590
|
|
|
403
591
|
if (!acceptingTouchStream) return false
|
|
404
592
|
val result = super.dispatchTouchEvent(event)
|
|
405
|
-
|
|
593
|
+
if ((action == MotionEvent.ACTION_DOWN && !result) ||
|
|
594
|
+
action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP
|
|
595
|
+
) {
|
|
596
|
+
clearTouchState()
|
|
597
|
+
}
|
|
406
598
|
return result
|
|
407
599
|
}
|
|
408
600
|
|
|
409
|
-
private fun retainTouchEvent(event: MotionEvent) {
|
|
410
|
-
retainedTouchEvent?.recycle()
|
|
411
|
-
retainedTouchEvent = MotionEvent.obtain(event)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
601
|
private fun clearTouchState() {
|
|
415
602
|
acceptingTouchStream = false
|
|
416
|
-
retainedTouchEvent?.recycle()
|
|
417
|
-
retainedTouchEvent = null
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private fun cancelAcceptedTouchStream() {
|
|
421
|
-
val latest = retainedTouchEvent
|
|
422
|
-
if (!acceptingTouchStream || latest == null) {
|
|
423
|
-
clearTouchState()
|
|
424
|
-
return
|
|
425
|
-
}
|
|
426
|
-
val cancel = MotionEvent.obtain(latest)
|
|
427
|
-
cancel.action = MotionEvent.ACTION_CANCEL
|
|
428
|
-
// Terminal BEFORE dispatch: a child's synchronous cancel handler can
|
|
429
|
-
// re-enter geometry application, and a nested emptiness transition
|
|
430
|
-
// must observe an already-ended stream instead of synthesizing a
|
|
431
|
-
// second cancel for it.
|
|
432
|
-
clearTouchState()
|
|
433
|
-
try {
|
|
434
|
-
super.dispatchTouchEvent(cancel)
|
|
435
|
-
} finally {
|
|
436
|
-
cancel.recycle()
|
|
437
|
-
}
|
|
438
603
|
}
|
|
439
604
|
|
|
440
605
|
fun setRequestedImportantForAccessibility(value: Int) {
|
|
@@ -442,14 +607,24 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
442
607
|
reapplyClipPresentation()
|
|
443
608
|
}
|
|
444
609
|
|
|
610
|
+
@DoNotStrip
|
|
611
|
+
fun setAutonomousMotion(active: Boolean) {
|
|
612
|
+
if (autonomousMotion == active) return
|
|
613
|
+
autonomousMotion = active
|
|
614
|
+
reapplyClipPresentation()
|
|
615
|
+
}
|
|
616
|
+
|
|
445
617
|
fun reapplyClipPresentation() {
|
|
446
|
-
val
|
|
618
|
+
val apertureVisible = apertureIntersectsHost()
|
|
619
|
+
val expectedVisibility = renderVisibility(
|
|
620
|
+
apertureVisible || shadowIntersectsHost(),
|
|
621
|
+
)
|
|
447
622
|
if (visibility != expectedVisibility) {
|
|
448
623
|
visibility = expectedVisibility
|
|
449
624
|
}
|
|
450
625
|
|
|
451
626
|
val expectedAccessibility = clipAccessibility(
|
|
452
|
-
|
|
627
|
+
autonomousMotion || !apertureVisible,
|
|
453
628
|
requestedImportantForAccessibility,
|
|
454
629
|
)
|
|
455
630
|
if (importantForAccessibility != expectedAccessibility) {
|
|
@@ -457,6 +632,29 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
457
632
|
}
|
|
458
633
|
}
|
|
459
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
|
+
|
|
460
658
|
fun resetClipState() {
|
|
461
659
|
clearTouchState()
|
|
462
660
|
commandIsAuthoritative = false
|
|
@@ -464,16 +662,27 @@ class SmoothClipView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
|
464
662
|
requestedY = 0f
|
|
465
663
|
requestedWidth = 0f
|
|
466
664
|
requestedHeight = 0f
|
|
467
|
-
|
|
665
|
+
requestedTopLeftRadius = 0f
|
|
666
|
+
requestedTopRightRadius = 0f
|
|
667
|
+
requestedBottomRightRadius = 0f
|
|
668
|
+
requestedBottomLeftRadius = 0f
|
|
669
|
+
requestedCurveCode = CLIP_CURVE_CIRCULAR
|
|
468
670
|
requestedContentTranslateX = 0f
|
|
469
671
|
requestedContentTranslateY = 0f
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
672
|
+
requestedContentScale = 1f
|
|
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()
|
|
685
|
+
clipPath.reset()
|
|
477
686
|
requestedImportantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
478
687
|
applyRequestedGeometry()
|
|
479
688
|
}
|