Fix issue where inset border radius was off when there is a border (#45658)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45658

The border radius of the inner "clear region" for inset shadows is based off of the border radius of the padding box path (i.e. the shadow path). Notably, this is not the View's given border radius iff there is a border present.

To get this "inner border radius" I added a new method inside of `CSSBackgroundDrawable`. This logic was already present [here](https://www.internalfb.com/code/fbsource/[33e35cbf387a]/xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java?lines=643-650), so I tried to share most of the code there. There may be a better way to do this, but this seems to be the quickest.

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D60083309

fbshipit-source-id: ace1ffa15fc3c0c09d4df096b604c9a2c91382c8
This commit is contained in:
Joe Vilches
2024-07-26 19:01:53 -07:00
committed by Facebook GitHub Bot
parent a694e4b325
commit 8fe3ae3ccc
2 changed files with 68 additions and 14 deletions
@@ -309,6 +309,12 @@ public class CSSBackgroundDrawable extends Drawable {
return mBorderRadius;
}
// Here, "inner" refers to the border radius on the inside of the border. So
// it ends up being the "outer" border radius inset by the respective width.
public float getInnerBorderRadius(float computedRadius, float borderWidth) {
return Math.max(computedRadius - borderWidth, 0);
}
public ComputedBorderRadius getComputedBorderRadius() {
return mComputedBorderRadius;
}
@@ -640,14 +646,16 @@ public class CSSBackgroundDrawable extends Drawable {
float bottomLeftRadius = mComputedBorderRadius.getBottomLeft();
float bottomRightRadius = mComputedBorderRadius.getBottomRight();
final float innerTopLeftRadiusX = Math.max(topLeftRadius - borderWidth.left, 0);
final float innerTopLeftRadiusY = Math.max(topLeftRadius - borderWidth.top, 0);
final float innerTopRightRadiusX = Math.max(topRightRadius - borderWidth.right, 0);
final float innerTopRightRadiusY = Math.max(topRightRadius - borderWidth.top, 0);
final float innerBottomRightRadiusX = Math.max(bottomRightRadius - borderWidth.right, 0);
final float innerBottomRightRadiusY = Math.max(bottomRightRadius - borderWidth.bottom, 0);
final float innerBottomLeftRadiusX = Math.max(bottomLeftRadius - borderWidth.left, 0);
final float innerBottomLeftRadiusY = Math.max(bottomLeftRadius - borderWidth.bottom, 0);
final float innerTopLeftRadiusX = getInnerBorderRadius(topLeftRadius, borderWidth.left);
final float innerTopLeftRadiusY = getInnerBorderRadius(topLeftRadius, borderWidth.top);
final float innerTopRightRadiusX = getInnerBorderRadius(topRightRadius, borderWidth.right);
final float innerTopRightRadiusY = getInnerBorderRadius(topRightRadius, borderWidth.top);
final float innerBottomRightRadiusX =
getInnerBorderRadius(bottomRightRadius, borderWidth.right);
final float innerBottomRightRadiusY =
getInnerBorderRadius(bottomRightRadius, borderWidth.bottom);
final float innerBottomLeftRadiusX = getInnerBorderRadius(bottomLeftRadius, borderWidth.left);
final float innerBottomLeftRadiusY = getInnerBorderRadius(bottomLeftRadius, borderWidth.bottom);
mInnerClipPathForBorderRadius.addRoundRect(
mInnerClipTempRectForBorderRadius,
@@ -18,7 +18,11 @@ import androidx.annotation.RequiresApi
import com.facebook.common.logging.FLog
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.uimanager.FilterHelper
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.style.BorderRadiusProp
import com.facebook.react.uimanager.style.BorderRadiusStyle
import kotlin.math.roundToInt
private const val TAG = "InsetBoxShadowDrawable"
@@ -76,13 +80,10 @@ internal class InsetBoxShadowDrawable(
clearRegionBounds.inset(spreadExtent, spreadExtent)
clearRegionBounds.offset(
PixelUtil.toPixelFromDIP(offsetX).roundToInt() + padding / 2,
PixelUtil.toPixelFromDIP(offsetY).roundToInt() + padding / 2)
PixelUtil.toPixelFromDIP(offsetY).roundToInt() + padding / 2,
)
val clearRegionBorderRadii =
getShadowBorderRadii(
-spreadExtent.toFloat(),
background.borderRadius,
clearRegionBounds.width().toFloat(),
clearRegionBounds.height().toFloat())
getClearRegionBorderRadii(spreadExtent, background, clearRegionBounds)
if (shadowPaint.colorFilter != colorFilter ||
clearRegionDrawable.layoutDirection != layoutDirection ||
@@ -129,4 +130,49 @@ internal class InsetBoxShadowDrawable(
canvas.restore()
}
}
private fun getClearRegionBorderRadii(
spread: Int,
background: CSSBackgroundDrawable,
clearRegionBounds: Rect,
): BorderRadiusStyle {
val computedBorderRadii = background.computedBorderRadius
val borderWidth = background.getDirectionAwareBorderInsets()
val topLeftRadius = computedBorderRadii.topLeft
val topRightRadius = computedBorderRadii.topRight
val bottomLeftRadius = computedBorderRadii.bottomLeft
val bottomRightRadius = computedBorderRadii.bottomRight
val innerTopLeftRadius = background.getInnerBorderRadius(topLeftRadius, borderWidth.left)
val innerTopRightRadius = background.getInnerBorderRadius(topRightRadius, borderWidth.right)
val innerBottomRightRadius =
background.getInnerBorderRadius(bottomRightRadius, borderWidth.right)
val innerBottomLeftRadius = background.getInnerBorderRadius(bottomLeftRadius, borderWidth.left)
val innerBorderRadii = BorderRadiusStyle()
innerBorderRadii.set(
BorderRadiusProp.BORDER_TOP_LEFT_RADIUS,
LengthPercentage(innerTopLeftRadius, LengthPercentageType.POINT),
)
innerBorderRadii.set(
BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS,
LengthPercentage(innerTopRightRadius, LengthPercentageType.POINT),
)
innerBorderRadii.set(
BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS,
LengthPercentage(innerBottomRightRadius, LengthPercentageType.POINT),
)
innerBorderRadii.set(
BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS,
LengthPercentage(innerBottomLeftRadius, LengthPercentageType.POINT),
)
return getShadowBorderRadii(
-spread.toFloat(),
innerBorderRadii,
clearRegionBounds.width().toFloat(),
clearRegionBounds.height().toFloat(),
)
}
}