Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.util.LayoutDirection
import androidx.annotation.RequiresApi
import com.facebook.react.modules.i18nmanager.I18nUtil
import com.facebook.react.uimanager.FilterHelper
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.style.BorderInsets
Expand All @@ -45,12 +49,8 @@ internal class InsetBoxShadowDrawable(
private val offsetY: Float,
private val blurRadius: Float,
private val spread: Float,
/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore user should invalidate this drawable when changing either of them
*/
var borderInsets: BorderInsets? = null,
var borderRadius: BorderRadiusStyle? = null,
borderInsets: BorderInsets? = null,
borderRadius: BorderRadiusStyle? = null,
) : Drawable() {
private val shadowPaint =
Paint().apply {
Expand All @@ -61,6 +61,43 @@ internal class InsetBoxShadowDrawable(
}
}

private var needUpdatePath = true
private var pathDensity = 0f
private var pathSwapsLeftAndRight = false
private val paddingBoxRect = RectF()
private val paddingBoxRadii = FloatArray(8)
private var hasPaddingBoxRadii = false
private val paddingBoxPath = Path()
private val innerRect = RectF()
private val innerRadii = FloatArray(8)
private val outerRect = RectF()

/*
* We assume borderRadius & borderInsets to be shared across multiple drawables
* therefore user should assign them to this drawable again when changing either of them
*/
var borderInsets: BorderInsets? = borderInsets
set(value) {
field = value
needUpdatePath = true
}

var borderRadius: BorderRadiusStyle? = borderRadius
set(value) {
field = value
needUpdatePath = true
}

override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
needUpdatePath = true
}

override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
needUpdatePath = true
return true
}

override fun setAlpha(alpha: Int) {
shadowPaint.alpha = (((alpha / 255f) * (Color.alpha(shadowColor) / 255f)) * 255f).roundToInt()
invalidateSelf()
Expand All @@ -82,69 +119,88 @@ internal class InsetBoxShadowDrawable(
}

override fun draw(canvas: Canvas) {
updatePath()

canvas.save().let { saveCount ->
if (hasPaddingBoxRadii) {
canvas.clipPath(paddingBoxPath)
canvas.drawDoubleRoundRect(outerRect, ZERO_RADII, innerRect, innerRadii, shadowPaint)
} else {
canvas.clipRect(paddingBoxRect)
canvas.drawDoubleRoundRect(outerRect, ZERO_RADII, innerRect, ZERO_RADII, shadowPaint)
}

canvas.restoreToCount(saveCount)
}
}

private fun updatePath() {
// Neither the density nor the RTL left/right swap preference notifies the drawable
val density = PixelUtil.getDisplayMetricDensity()
val swapsLeftAndRight =
layoutDirection == LayoutDirection.RTL &&
(borderRadius != null || borderInsets != null) &&
I18nUtil.instance.doLeftAndRightSwapInRTL(context)
if (!needUpdatePath && density == pathDensity && swapsLeftAndRight == pathSwapsLeftAndRight) {
return
}
needUpdatePath = false
pathDensity = density
pathSwapsLeftAndRight = swapsLeftAndRight

val computedBorderRadii = computeBorderRadii()
val computedBorderInsets = computeBorderInsets()

val paddingBoxRect =
RectF(
bounds.left + (computedBorderInsets?.left ?: 0f),
bounds.top + (computedBorderInsets?.top ?: 0f),
bounds.right - (computedBorderInsets?.right ?: 0f),
bounds.bottom - (computedBorderInsets?.bottom ?: 0f),
)
val paddingBoxRadii =
computedBorderRadii?.let {
floatArrayOf(
innerRadius(it.topLeft.horizontal, computedBorderInsets?.left),
innerRadius(it.topLeft.vertical, computedBorderInsets?.top),
innerRadius(it.topRight.horizontal, computedBorderInsets?.right),
innerRadius(it.topRight.vertical, computedBorderInsets?.top),
innerRadius(it.bottomRight.horizontal, computedBorderInsets?.right),
innerRadius(it.bottomRight.vertical, computedBorderInsets?.bottom),
innerRadius(it.bottomLeft.horizontal, computedBorderInsets?.left),
innerRadius(it.bottomLeft.vertical, computedBorderInsets?.bottom),
)
}
paddingBoxRect.set(
bounds.left + (computedBorderInsets?.left ?: 0f),
bounds.top + (computedBorderInsets?.top ?: 0f),
bounds.right - (computedBorderInsets?.right ?: 0f),
bounds.bottom - (computedBorderInsets?.bottom ?: 0f),
)
hasPaddingBoxRadii = computedBorderRadii != null
if (computedBorderRadii != null) {
paddingBoxRadii[0] =
innerRadius(computedBorderRadii.topLeft.horizontal, computedBorderInsets?.left)
paddingBoxRadii[1] =
innerRadius(computedBorderRadii.topLeft.vertical, computedBorderInsets?.top)
paddingBoxRadii[2] =
innerRadius(computedBorderRadii.topRight.horizontal, computedBorderInsets?.right)
paddingBoxRadii[3] =
innerRadius(computedBorderRadii.topRight.vertical, computedBorderInsets?.top)
paddingBoxRadii[4] =
innerRadius(computedBorderRadii.bottomRight.horizontal, computedBorderInsets?.right)
paddingBoxRadii[5] =
innerRadius(computedBorderRadii.bottomRight.vertical, computedBorderInsets?.bottom)
paddingBoxRadii[6] =
innerRadius(computedBorderRadii.bottomLeft.horizontal, computedBorderInsets?.left)
paddingBoxRadii[7] =
innerRadius(computedBorderRadii.bottomLeft.vertical, computedBorderInsets?.bottom)
}

val x = offsetX.dpToPx()
val y = offsetY.dpToPx()
val spreadExtent = spread.dpToPx()
val innerRect =
RectF(paddingBoxRect).apply {
if (2 * spreadExtent > paddingBoxRect.width()) {
setEmpty()
} else {
inset(spreadExtent, spreadExtent)
}
offset(x, y)
}
innerRect.set(paddingBoxRect)
if (2 * spreadExtent > paddingBoxRect.width()) {
innerRect.setEmpty()
} else {
innerRect.inset(spreadExtent, spreadExtent)
}
innerRect.offset(x, y)

// Graciously stolen from Blink
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/box_painter_base.cc;l=338;drc=0a301506035e13015ea5c8dd39164d0d5954fa60
val blurExtent = FilterHelper.sigmaToRadius(blurRadius)
val outerRect =
RectF(innerRect).apply {
set(paddingBoxRect)
inset(-blurExtent, -blurExtent)
union(RectF(innerRect))
}

canvas.save().let { saveCount ->
if (paddingBoxRadii != null) {
canvas.clipPath(
Path().apply { addRoundRect(paddingBoxRect, paddingBoxRadii, Path.Direction.CW) },
)

val innerRadii =
paddingBoxRadii.map { adjustRadiusForSpread(it, -spreadExtent) }.toFloatArray()

canvas.drawDoubleRoundRect(outerRect, ZERO_RADII, innerRect, innerRadii, shadowPaint)
} else {
canvas.clipRect(paddingBoxRect)
canvas.drawDoubleRoundRect(outerRect, ZERO_RADII, innerRect, ZERO_RADII, shadowPaint)
outerRect.set(paddingBoxRect)
outerRect.inset(-blurExtent, -blurExtent)
outerRect.union(innerRect)

if (hasPaddingBoxRadii) {
paddingBoxPath.rewind()
paddingBoxPath.addRoundRect(paddingBoxRect, paddingBoxRadii, Path.Direction.CW)
for (index in innerRadii.indices) {
innerRadii[index] = adjustRadiusForSpread(paddingBoxRadii[index], -spreadExtent)
}

canvas.restoreToCount(saveCount)
}
}

Expand Down
Loading
Loading