From 8fe3ae3ccc7777dfc505bc62375eb4156e7dac18 Mon Sep 17 00:00:00 2001 From: Joe Vilches Date: Fri, 26 Jul 2024 19:01:53 -0700 Subject: [PATCH] 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 --- .../drawable/CSSBackgroundDrawable.java | 24 +++++--- .../drawable/InsetBoxShadowDrawable.kt | 58 +++++++++++++++++-- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java index 7369bc9de442db..90e501ba112508 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java @@ -309,6 +309,12 @@ public BorderRadiusStyle getBorderRadius() { 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 @@ private void updatePath() { 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, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/InsetBoxShadowDrawable.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/InsetBoxShadowDrawable.kt index 6197c920cec382..e4de5d921fe77d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/InsetBoxShadowDrawable.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/InsetBoxShadowDrawable.kt @@ -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(), + ) + } }