From c710386e72c4957ac95a19e15f95a71c3f4060cd Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 13 Apr 2024 15:34:12 -0700 Subject: [PATCH] Encapsulate border radius resolution logic (#43983) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43983 We duplicate some pretty hairy code related to conversion between logical and physical edges, along with the grafting between uniform and non uniform radii. This encapsulates border radius resolution/assignment logic. Changelog: [Internal] Reviewed By: alanleedev Differential Revision: D55635743 fbshipit-source-id: 906c35af2bf18f0586d71d05f9cf61d4248ede1e --- .../ReactAndroid/api/ReactAndroid.api | 116 +++++++++-- .../com/facebook/react/uimanager/FloatUtil.kt | 11 ++ .../drawable/CSSBackgroundDrawable.java | 180 +++--------------- .../uimanager/style/BorderRadiusStyle.kt | 131 +++++++++++++ .../uimanager/style/ComputedBorderRadius.kt | 20 ++ .../react/views/view/ReactViewGroup.java | 126 +++--------- 6 files changed, 314 insertions(+), 270 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/ComputedBorderRadius.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 6d9d299bb9c..d787e503df2 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -4081,6 +4081,7 @@ public abstract interface class com/facebook/react/uimanager/FabricViewStateMana public final class com/facebook/react/uimanager/FloatUtil { public static final field INSTANCE Lcom/facebook/react/uimanager/FloatUtil; public static final fun floatsEqual (FF)Z + public static final fun floatsEqual (Ljava/lang/Float;Ljava/lang/Float;)Z } public abstract class com/facebook/react/uimanager/GuardedFrameCallback : android/view/Choreographer$FrameCallback { @@ -5454,11 +5455,9 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro public fun draw (Landroid/graphics/Canvas;)V public fun getAlpha ()I public fun getBorderColor (I)I - public fun getBorderRadius (Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation;)F - public fun getBorderRadiusOrDefaultTo (FLcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation;)F + public fun getBorderRadius ()Lcom/facebook/react/uimanager/style/BorderRadiusStyle; public fun getBorderWidthOrDefaultTo (FI)F public fun getDirectionAwareBorderInsets ()Landroid/graphics/RectF; - public fun getFullBorderRadius ()F public fun getFullBorderWidth ()F public fun getOpacity ()I public fun getOutline (Landroid/graphics/Outline;)V @@ -5469,6 +5468,8 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro public fun paddingBoxPath ()Landroid/graphics/Path; public fun setAlpha (I)V public fun setBorderColor (IFF)V + public fun setBorderRadius (Lcom/facebook/react/uimanager/style/BorderRadiusProp;Ljava/lang/Float;)V + public fun setBorderRadius (Lcom/facebook/react/uimanager/style/BorderRadiusStyle;)V public fun setBorderStyle (Ljava/lang/String;)V public fun setBorderWidth (IF)V public fun setColor (I)V @@ -5478,23 +5479,6 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro public fun setResolvedLayoutDirection (I)Z } -public final class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation : java/lang/Enum { - public static final field BOTTOM_END Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field BOTTOM_LEFT Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field BOTTOM_RIGHT Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field BOTTOM_START Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field END_END Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field END_START Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field START_END Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field START_START Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field TOP_END Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field TOP_LEFT Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field TOP_RIGHT Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static final field TOP_START Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; - public static fun values ()[Lcom/facebook/react/uimanager/drawable/CSSBackgroundDrawable$BorderRadiusLocation; -} - public abstract interface class com/facebook/react/uimanager/events/BatchEventDispatchedListener { public abstract fun onBatchEventDispatched ()V } @@ -5784,6 +5768,97 @@ public abstract interface class com/facebook/react/uimanager/layoutanimation/Lay public abstract fun onAnimationEnd ()V } +public final class com/facebook/react/uimanager/style/BorderRadiusProp : java/lang/Enum { + public static final field BORDER_BOTTOM_END_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_BOTTOM_LEFT_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_BOTTOM_RIGHT_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_BOTTOM_START_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_END_END_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_END_START_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_START_END_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_START_START_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_TOP_END_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_TOP_LEFT_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_TOP_RIGHT_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static final field BORDER_TOP_START_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static fun values ()[Lcom/facebook/react/uimanager/style/BorderRadiusProp; +} + +public final class com/facebook/react/uimanager/style/BorderRadiusStyle { + public fun ()V + public fun (Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;)V + public synthetic fun (Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/lang/Float; + public final fun component10 ()Ljava/lang/Float; + public final fun component11 ()Ljava/lang/Float; + public final fun component12 ()Ljava/lang/Float; + public final fun component13 ()Ljava/lang/Float; + public final fun component2 ()Ljava/lang/Float; + public final fun component3 ()Ljava/lang/Float; + public final fun component4 ()Ljava/lang/Float; + public final fun component5 ()Ljava/lang/Float; + public final fun component6 ()Ljava/lang/Float; + public final fun component7 ()Ljava/lang/Float; + public final fun component8 ()Ljava/lang/Float; + public final fun component9 ()Ljava/lang/Float; + public final fun copy (Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;)Lcom/facebook/react/uimanager/style/BorderRadiusStyle; + public static synthetic fun copy$default (Lcom/facebook/react/uimanager/style/BorderRadiusStyle;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/Float;ILjava/lang/Object;)Lcom/facebook/react/uimanager/style/BorderRadiusStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Lcom/facebook/react/uimanager/style/BorderRadiusProp;)Ljava/lang/Float; + public final fun getBottomEnd ()Ljava/lang/Float; + public final fun getBottomLeft ()Ljava/lang/Float; + public final fun getBottomRight ()Ljava/lang/Float; + public final fun getBottomStart ()Ljava/lang/Float; + public final fun getEndEnd ()Ljava/lang/Float; + public final fun getEndStart ()Ljava/lang/Float; + public final fun getStartEnd ()Ljava/lang/Float; + public final fun getStartStart ()Ljava/lang/Float; + public final fun getTopEnd ()Ljava/lang/Float; + public final fun getTopLeft ()Ljava/lang/Float; + public final fun getTopRight ()Ljava/lang/Float; + public final fun getTopStart ()Ljava/lang/Float; + public final fun getUniform ()Ljava/lang/Float; + public final fun hasRoundedBorders ()Z + public fun hashCode ()I + public final fun resolve (ILandroid/content/Context;)Lcom/facebook/react/uimanager/style/ComputedBorderRadius; + public final fun set (Lcom/facebook/react/uimanager/style/BorderRadiusProp;Ljava/lang/Float;)V + public final fun setBottomEnd (Ljava/lang/Float;)V + public final fun setBottomLeft (Ljava/lang/Float;)V + public final fun setBottomRight (Ljava/lang/Float;)V + public final fun setBottomStart (Ljava/lang/Float;)V + public final fun setEndEnd (Ljava/lang/Float;)V + public final fun setEndStart (Ljava/lang/Float;)V + public final fun setStartEnd (Ljava/lang/Float;)V + public final fun setStartStart (Ljava/lang/Float;)V + public final fun setTopEnd (Ljava/lang/Float;)V + public final fun setTopLeft (Ljava/lang/Float;)V + public final fun setTopRight (Ljava/lang/Float;)V + public final fun setTopStart (Ljava/lang/Float;)V + public final fun setUniform (Ljava/lang/Float;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/facebook/react/uimanager/style/ComputedBorderRadius { + public fun (FFFF)V + public final fun component1 ()F + public final fun component2 ()F + public final fun component3 ()F + public final fun component4 ()F + public final fun copy (FFFF)Lcom/facebook/react/uimanager/style/ComputedBorderRadius; + public static synthetic fun copy$default (Lcom/facebook/react/uimanager/style/ComputedBorderRadius;FFFFILjava/lang/Object;)Lcom/facebook/react/uimanager/style/ComputedBorderRadius; + public fun equals (Ljava/lang/Object;)Z + public final fun getBottomLeft ()F + public final fun getBottomRight ()F + public final fun getTopLeft ()F + public final fun getTopRight ()F + public final fun hasRoundedBorders ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public class com/facebook/react/uimanager/util/ReactFindViewUtil { public fun ()V public static fun addViewListener (Lcom/facebook/react/uimanager/util/ReactFindViewUtil$OnViewFoundListener;)V @@ -7807,6 +7882,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro public fun setBorderColor (IFF)V public fun setBorderRadius (F)V public fun setBorderRadius (FI)V + public fun setBorderRadius (Lcom/facebook/react/uimanager/style/BorderRadiusProp;Ljava/lang/Float;)V public fun setBorderStyle (Ljava/lang/String;)V public fun setBorderWidth (IF)V public fun setHitSlopRect (Landroid/graphics/Rect;)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FloatUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FloatUtil.kt index e5ad28d758a..732c728122d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FloatUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FloatUtil.kt @@ -18,4 +18,15 @@ public object FloatUtil { java.lang.Float.isNaN(f1) && java.lang.Float.isNaN(f2) } else abs(f2 - f1) < EPSILON } + + @JvmStatic + public fun floatsEqual(f1: Float?, f2: Float?): Boolean { + if (f1 == null) { + return f2 == null + } else if (f2 == null) { + return false + } + + return floatsEqual(f1, f2) + } } 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 83d598d9767..21094fc73d5 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 @@ -28,7 +28,9 @@ import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.FloatUtil; import com.facebook.react.uimanager.Spacing; -import java.util.Arrays; +import com.facebook.react.uimanager.style.BorderRadiusProp; +import com.facebook.react.uimanager.style.BorderRadiusStyle; +import com.facebook.react.uimanager.style.ComputedBorderRadius; import java.util.Locale; /** @@ -99,7 +101,6 @@ public class CSSBackgroundDrawable extends Drawable { private @Nullable PointF mInnerBottomRightCorner; private @Nullable PointF mInnerBottomLeftCorner; private boolean mNeedUpdatePathForBorderRadius = false; - private float mBorderRadius = Float.NaN; /* Used by all types of background and for drawing borders */ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -112,25 +113,10 @@ public class CSSBackgroundDrawable extends Drawable { // the paths, overlapping them and closing the visible gap. private final float mGapBetweenPaths = 0.8f; - private @Nullable float[] mBorderCornerRadii; + private BorderRadiusStyle mBorderRadius = new BorderRadiusStyle(); private final Context mContext; private int mLayoutDirection; - public enum BorderRadiusLocation { - TOP_LEFT, - TOP_RIGHT, - BOTTOM_RIGHT, - BOTTOM_LEFT, - TOP_START, - TOP_END, - BOTTOM_START, - BOTTOM_END, - END_END, - END_START, - START_END, - START_START - } - public CSSBackgroundDrawable(Context context) { mContext = context; } @@ -146,19 +132,7 @@ public class CSSBackgroundDrawable extends Drawable { } public boolean hasRoundedBorders() { - if (!Float.isNaN(mBorderRadius) && mBorderRadius > 0) { - return true; - } - - if (mBorderCornerRadii != null) { - for (final float borderRadii : mBorderCornerRadii) { - if (!Float.isNaN(borderRadii) && borderRadii > 0) { - return true; - } - } - } - - return false; + return mBorderRadius.hasRoundedBorders(); } @Override @@ -193,7 +167,7 @@ public class CSSBackgroundDrawable extends Drawable { /* Android's elevation implementation requires this to be implemented to know where to draw the shadow. */ @Override public void getOutline(Outline outline) { - if ((!Float.isNaN(mBorderRadius) && mBorderRadius > 0) || mBorderCornerRadii != null) { + if (hasRoundedBorders()) { updatePath(); outline.setConvexPath(mPathForBorderRadiusOutline); @@ -260,48 +234,36 @@ public class CSSBackgroundDrawable extends Drawable { } } + /** + * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead. + */ public void setRadius(float radius) { - if (!FloatUtil.floatsEqual(mBorderRadius, radius)) { - mBorderRadius = radius; - mNeedUpdatePathForBorderRadius = true; - invalidateSelf(); - } + @Nullable Float boxedRadius = Float.isNaN(radius) ? null : Float.valueOf(radius); + setBorderRadius(BorderRadiusProp.BORDER_RADIUS, boxedRadius); } + /** + * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead. + */ public void setRadius(float radius, int position) { - if (mBorderCornerRadii == null) { - mBorderCornerRadii = new float[12]; - Arrays.fill(mBorderCornerRadii, Float.NaN); - } + @Nullable Float boxedRadius = Float.isNaN(radius) ? null : Float.valueOf(radius); + setBorderRadius(BorderRadiusProp.values()[position], boxedRadius); + } - if (!FloatUtil.floatsEqual(mBorderCornerRadii[position], radius)) { - mBorderCornerRadii[position] = radius; + public void setBorderRadius(BorderRadiusProp property, @Nullable Float radius) { + if (!FloatUtil.floatsEqual(mBorderRadius.getUniform(), radius)) { + mBorderRadius.set(property, radius); mNeedUpdatePathForBorderRadius = true; invalidateSelf(); } } - public float getFullBorderRadius() { - return Float.isNaN(mBorderRadius) ? 0 : mBorderRadius; + public void setBorderRadius(BorderRadiusStyle radius) { + mBorderRadius = radius; } - public float getBorderRadius(final BorderRadiusLocation location) { - return getBorderRadiusOrDefaultTo(Float.NaN, location); - } - - public float getBorderRadiusOrDefaultTo( - final float defaultValue, final BorderRadiusLocation location) { - if (mBorderCornerRadii == null) { - return defaultValue; - } - - final float radius = mBorderCornerRadii[location.ordinal()]; - - if (Float.isNaN(radius)) { - return defaultValue; - } - - return radius; + public BorderRadiusStyle getBorderRadius() { + return mBorderRadius; } public void setColor(int color) { @@ -606,95 +568,11 @@ public class CSSBackgroundDrawable extends Drawable { mTempRectForCenterDrawPath.left += borderWidth.left * 0.5f; mTempRectForCenterDrawPath.right -= borderWidth.right * 0.5f; - final float borderRadius = getFullBorderRadius(); - float topLeftRadius = getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.TOP_LEFT); - float topRightRadius = getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.TOP_RIGHT); - float bottomLeftRadius = - getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.BOTTOM_LEFT); - float bottomRightRadius = - getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.BOTTOM_RIGHT); - - final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - float topStartRadius = getBorderRadius(BorderRadiusLocation.TOP_START); - float topEndRadius = getBorderRadius(BorderRadiusLocation.TOP_END); - float bottomStartRadius = getBorderRadius(BorderRadiusLocation.BOTTOM_START); - float bottomEndRadius = getBorderRadius(BorderRadiusLocation.BOTTOM_END); - - float endEndRadius = getBorderRadius(BorderRadiusLocation.END_END); - float endStartRadius = getBorderRadius(BorderRadiusLocation.END_START); - float startEndRadius = getBorderRadius(BorderRadiusLocation.START_END); - float startStartRadius = getBorderRadius(BorderRadiusLocation.START_START); - - if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) { - if (Float.isNaN(topStartRadius)) { - topStartRadius = topLeftRadius; - } - - if (Float.isNaN(topEndRadius)) { - topEndRadius = topRightRadius; - } - - if (Float.isNaN(bottomStartRadius)) { - bottomStartRadius = bottomLeftRadius; - } - - if (Float.isNaN(bottomEndRadius)) { - bottomEndRadius = bottomRightRadius; - } - - final float logicalTopStartRadius = - Float.isNaN(topStartRadius) ? startStartRadius : topStartRadius; - final float logicalTopEndRadius = Float.isNaN(topEndRadius) ? startEndRadius : topEndRadius; - final float logicalBottomStartRadius = - Float.isNaN(bottomStartRadius) ? endStartRadius : bottomStartRadius; - final float logicalBottomEndRadius = - Float.isNaN(bottomEndRadius) ? endEndRadius : bottomEndRadius; - - final float directionAwareTopLeftRadius = isRTL ? logicalTopEndRadius : logicalTopStartRadius; - final float directionAwareTopRightRadius = - isRTL ? logicalTopStartRadius : logicalTopEndRadius; - final float directionAwareBottomLeftRadius = - isRTL ? logicalBottomEndRadius : logicalBottomStartRadius; - final float directionAwareBottomRightRadius = - isRTL ? logicalBottomStartRadius : logicalBottomEndRadius; - - topLeftRadius = directionAwareTopLeftRadius; - topRightRadius = directionAwareTopRightRadius; - bottomLeftRadius = directionAwareBottomLeftRadius; - bottomRightRadius = directionAwareBottomRightRadius; - } else { - final float logicalTopStartRadius = - Float.isNaN(topStartRadius) ? startStartRadius : topStartRadius; - final float logicalTopEndRadius = Float.isNaN(topEndRadius) ? startEndRadius : topEndRadius; - final float logicalBottomStartRadius = - Float.isNaN(bottomStartRadius) ? endStartRadius : bottomStartRadius; - final float logicalBottomEndRadius = - Float.isNaN(bottomEndRadius) ? endEndRadius : bottomEndRadius; - - final float directionAwareTopLeftRadius = isRTL ? logicalTopEndRadius : logicalTopStartRadius; - final float directionAwareTopRightRadius = - isRTL ? logicalTopStartRadius : logicalTopEndRadius; - final float directionAwareBottomLeftRadius = - isRTL ? logicalBottomEndRadius : logicalBottomStartRadius; - final float directionAwareBottomRightRadius = - isRTL ? logicalBottomStartRadius : logicalBottomEndRadius; - - if (!Float.isNaN(directionAwareTopLeftRadius)) { - topLeftRadius = directionAwareTopLeftRadius; - } - - if (!Float.isNaN(directionAwareTopRightRadius)) { - topRightRadius = directionAwareTopRightRadius; - } - - if (!Float.isNaN(directionAwareBottomLeftRadius)) { - bottomLeftRadius = directionAwareBottomLeftRadius; - } - - if (!Float.isNaN(directionAwareBottomRightRadius)) { - bottomRightRadius = directionAwareBottomRightRadius; - } - } + ComputedBorderRadius radius = mBorderRadius.resolve(mLayoutDirection, mContext); + float topLeftRadius = radius.getTopLeft(); + float topRightRadius = radius.getTopRight(); + float bottomLeftRadius = radius.getBottomLeft(); + float bottomRightRadius = radius.getBottomRight(); final float innerTopLeftRadiusX = Math.max(topLeftRadius - borderWidth.left, 0); final float innerTopLeftRadiusY = Math.max(topLeftRadius - borderWidth.top, 0); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt new file mode 100644 index 00000000000..0683b01b073 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager.style + +import android.content.Context +import android.util.LayoutDirection +import com.facebook.react.modules.i18nmanager.I18nUtil + +/** Represents the collection of possible border radius style properties. */ +public enum class BorderRadiusProp { + BORDER_TOP_LEFT_RADIUS, + BORDER_TOP_RIGHT_RADIUS, + BORDER_BOTTOM_RIGHT_RADIUS, + BORDER_BOTTOM_LEFT_RADIUS, + BORDER_TOP_START_RADIUS, + BORDER_TOP_END_RADIUS, + BORDER_BOTTOM_START_RADIUS, + BORDER_BOTTOM_END_RADIUS, + BORDER_END_END_RADIUS, + BORDER_END_START_RADIUS, + BORDER_START_END_RADIUS, + BORDER_START_START_RADIUS, + BORDER_RADIUS, +} + +/** Represents all logical properties and shorthands for border radius. */ +public data class BorderRadiusStyle( + var uniform: Float? = null, + var topLeft: Float? = null, + var topRight: Float? = null, + var bottomLeft: Float? = null, + var bottomRight: Float? = null, + var topStart: Float? = null, + var topEnd: Float? = null, + var bottomStart: Float? = null, + var bottomEnd: Float? = null, + var startStart: Float? = null, + var startEnd: Float? = null, + var endStart: Float? = null, + var endEnd: Float? = null +) { + public constructor(properties: List>) : this() { + properties.forEach { (k, v) -> set(k, v) } + } + + public fun set(property: BorderRadiusProp, value: Float?) { + when (property) { + BorderRadiusProp.BORDER_RADIUS -> uniform = value + BorderRadiusProp.BORDER_TOP_LEFT_RADIUS -> topLeft = value + BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS -> topRight = value + BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS -> bottomLeft = value + BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS -> bottomRight = value + BorderRadiusProp.BORDER_TOP_START_RADIUS -> topStart = value + BorderRadiusProp.BORDER_TOP_END_RADIUS -> topEnd = value + BorderRadiusProp.BORDER_BOTTOM_START_RADIUS -> bottomStart = value + BorderRadiusProp.BORDER_BOTTOM_END_RADIUS -> bottomEnd = value + BorderRadiusProp.BORDER_START_START_RADIUS -> startStart = value + BorderRadiusProp.BORDER_START_END_RADIUS -> startEnd = value + BorderRadiusProp.BORDER_END_START_RADIUS -> endStart = value + BorderRadiusProp.BORDER_END_END_RADIUS -> endEnd = value + } + } + + public fun get(property: BorderRadiusProp): Float? { + return when (property) { + BorderRadiusProp.BORDER_RADIUS -> uniform + BorderRadiusProp.BORDER_TOP_LEFT_RADIUS -> topLeft + BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS -> topRight + BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS -> bottomLeft + BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS -> bottomRight + BorderRadiusProp.BORDER_TOP_START_RADIUS -> topStart + BorderRadiusProp.BORDER_TOP_END_RADIUS -> topEnd + BorderRadiusProp.BORDER_BOTTOM_START_RADIUS -> bottomStart + BorderRadiusProp.BORDER_BOTTOM_END_RADIUS -> bottomEnd + BorderRadiusProp.BORDER_START_START_RADIUS -> startStart + BorderRadiusProp.BORDER_START_END_RADIUS -> startEnd + BorderRadiusProp.BORDER_END_START_RADIUS -> endStart + BorderRadiusProp.BORDER_END_END_RADIUS -> endEnd + } + } + + public fun hasRoundedBorders(): Boolean { + return ((uniform ?: 0f) > 0f) || + ((topLeft ?: 0f) > 0f) || + ((topRight ?: 0f) > 0f) || + ((bottomLeft ?: 0f) > 0f) || + ((bottomRight ?: 0f) > 0f) || + ((topStart ?: 0f) > 0f) || + ((topEnd ?: 0f) > 0f) || + ((bottomStart ?: 0f) > 0f) || + ((bottomEnd ?: 0f) > 0f) || + ((startStart ?: 0f) > 0f) || + ((startEnd ?: 0f) > 0f) || + ((endStart ?: 0f) > 0f) || + ((endEnd ?: 0f) > 0f) + } + + public fun resolve( + layoutDirection: Int, + context: Context, + ): ComputedBorderRadius { + return when (layoutDirection) { + LayoutDirection.LTR -> + ComputedBorderRadius( + topLeft = startStart ?: topStart ?: topLeft ?: uniform ?: 0f, + topRight = endStart ?: topEnd ?: topRight ?: uniform ?: 0f, + bottomLeft = startEnd ?: bottomStart ?: bottomLeft ?: uniform ?: 0f, + bottomRight = endEnd ?: bottomEnd ?: bottomRight ?: uniform ?: 0f) + LayoutDirection.RTL -> + if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(context)) { + ComputedBorderRadius( + topLeft = endStart ?: topEnd ?: topRight ?: uniform ?: 0f, + topRight = startStart ?: topStart ?: topLeft ?: uniform ?: 0f, + bottomLeft = endEnd ?: bottomStart ?: bottomRight ?: uniform ?: 0f, + bottomRight = startEnd ?: bottomEnd ?: bottomLeft ?: uniform ?: 0f) + } else { + ComputedBorderRadius( + topLeft = endStart ?: topEnd ?: topLeft ?: uniform ?: 0f, + topRight = startStart ?: topStart ?: topRight ?: uniform ?: 0f, + bottomLeft = endEnd ?: bottomStart ?: bottomLeft ?: uniform ?: 0f, + bottomRight = startEnd ?: bottomEnd ?: bottomRight ?: uniform ?: 0f) + } + else -> throw IllegalArgumentException("Expected resolved layout direction") + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/ComputedBorderRadius.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/ComputedBorderRadius.kt new file mode 100644 index 00000000000..181bc4ecba0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/ComputedBorderRadius.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager.style + +/** Phsysical edge lengths (in DIPs) for a border-radius. */ +public data class ComputedBorderRadius( + val topLeft: Float, + val topRight: Float, + val bottomLeft: Float, + val bottomRight: Float, +) { + public fun hasRoundedBorders(): Boolean { + return topLeft > 0f || topRight > 0f || bottomLeft > 0f || bottomRight > 0f + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 7426dfb4518..cd467967563 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -52,7 +52,9 @@ import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.common.ViewUtil; -import com.facebook.yoga.YogaConstants; +import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable; +import com.facebook.react.uimanager.style.BorderRadiusProp; +import com.facebook.react.uimanager.style.ComputedBorderRadius; /** * Backing for a React View. Has support for borders, but since borders aren't common, lazy @@ -315,16 +317,27 @@ public class ReactViewGroup extends ViewGroup getOrCreateReactViewBackground().setBorderColor(position, rgb, alpha); } + /** + * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead. + */ public void setBorderRadius(float borderRadius) { - ReactViewBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); + CSSBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); backgroundDrawable.setRadius(borderRadius); } + /** + * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead. + */ public void setBorderRadius(float borderRadius, int position) { - ReactViewBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); + CSSBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); backgroundDrawable.setRadius(borderRadius, position); } + public void setBorderRadius(BorderRadiusProp property, @Nullable Float borderRadius) { + CSSBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); + backgroundDrawable.setBorderRadius(property, borderRadius); + } + public void setBorderStyle(@Nullable String style) { getOrCreateReactViewBackground().setBorderStyle(style); } @@ -912,95 +925,10 @@ public class ReactViewGroup extends ViewGroup bottom -= borderWidth.bottom; } - final float borderRadius = mReactBackgroundDrawable.getFullBorderRadius(); - float topLeftBorderRadius = - mReactBackgroundDrawable.getBorderRadiusOrDefaultTo( - borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_LEFT); - float topRightBorderRadius = - mReactBackgroundDrawable.getBorderRadiusOrDefaultTo( - borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_RIGHT); - float bottomLeftBorderRadius = - mReactBackgroundDrawable.getBorderRadiusOrDefaultTo( - borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_LEFT); - float bottomRightBorderRadius = - mReactBackgroundDrawable.getBorderRadiusOrDefaultTo( - borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_RIGHT); + final ComputedBorderRadius borderRadius = + mReactBackgroundDrawable.getBorderRadius().resolve(mLayoutDirection, getContext()); - final boolean isRTL = mLayoutDirection == View.LAYOUT_DIRECTION_RTL; - float topStartBorderRadius = - mReactBackgroundDrawable.getBorderRadius( - ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_START); - float topEndBorderRadius = - mReactBackgroundDrawable.getBorderRadius( - ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_END); - float bottomStartBorderRadius = - mReactBackgroundDrawable.getBorderRadius( - ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_START); - float bottomEndBorderRadius = - mReactBackgroundDrawable.getBorderRadius( - ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_END); - - if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(getContext())) { - if (YogaConstants.isUndefined(topStartBorderRadius)) { - topStartBorderRadius = topLeftBorderRadius; - } - - if (YogaConstants.isUndefined(topEndBorderRadius)) { - topEndBorderRadius = topRightBorderRadius; - } - - if (YogaConstants.isUndefined(bottomStartBorderRadius)) { - bottomStartBorderRadius = bottomLeftBorderRadius; - } - - if (YogaConstants.isUndefined(bottomEndBorderRadius)) { - bottomEndBorderRadius = bottomRightBorderRadius; - } - - final float directionAwareTopLeftRadius = - isRTL ? topEndBorderRadius : topStartBorderRadius; - final float directionAwareTopRightRadius = - isRTL ? topStartBorderRadius : topEndBorderRadius; - final float directionAwareBottomLeftRadius = - isRTL ? bottomEndBorderRadius : bottomStartBorderRadius; - final float directionAwareBottomRightRadius = - isRTL ? bottomStartBorderRadius : bottomEndBorderRadius; - - topLeftBorderRadius = directionAwareTopLeftRadius; - topRightBorderRadius = directionAwareTopRightRadius; - bottomLeftBorderRadius = directionAwareBottomLeftRadius; - bottomRightBorderRadius = directionAwareBottomRightRadius; - } else { - final float directionAwareTopLeftRadius = - isRTL ? topEndBorderRadius : topStartBorderRadius; - final float directionAwareTopRightRadius = - isRTL ? topStartBorderRadius : topEndBorderRadius; - final float directionAwareBottomLeftRadius = - isRTL ? bottomEndBorderRadius : bottomStartBorderRadius; - final float directionAwareBottomRightRadius = - isRTL ? bottomStartBorderRadius : bottomEndBorderRadius; - - if (!YogaConstants.isUndefined(directionAwareTopLeftRadius)) { - topLeftBorderRadius = directionAwareTopLeftRadius; - } - - if (!YogaConstants.isUndefined(directionAwareTopRightRadius)) { - topRightBorderRadius = directionAwareTopRightRadius; - } - - if (!YogaConstants.isUndefined(directionAwareBottomLeftRadius)) { - bottomLeftBorderRadius = directionAwareBottomLeftRadius; - } - - if (!YogaConstants.isUndefined(directionAwareBottomRightRadius)) { - bottomRightBorderRadius = directionAwareBottomRightRadius; - } - } - - if (topLeftBorderRadius > 0 - || topRightBorderRadius > 0 - || bottomRightBorderRadius > 0 - || bottomLeftBorderRadius > 0) { + if (borderRadius.hasRoundedBorders()) { if (mPath == null) { mPath = new Path(); } @@ -1009,14 +937,14 @@ public class ReactViewGroup extends ViewGroup mPath.addRoundRect( new RectF(left, top, right, bottom), new float[] { - Math.max(topLeftBorderRadius - borderWidth.left, 0), - Math.max(topLeftBorderRadius - borderWidth.top, 0), - Math.max(topRightBorderRadius - borderWidth.right, 0), - Math.max(topRightBorderRadius - borderWidth.top, 0), - Math.max(bottomRightBorderRadius - borderWidth.right, 0), - Math.max(bottomRightBorderRadius - borderWidth.bottom, 0), - Math.max(bottomLeftBorderRadius - borderWidth.left, 0), - Math.max(bottomLeftBorderRadius - borderWidth.bottom, 0), + Math.max(borderRadius.getTopLeft() - borderWidth.left, 0), + Math.max(borderRadius.getTopLeft() - borderWidth.top, 0), + Math.max(borderRadius.getTopRight() - borderWidth.right, 0), + Math.max(borderRadius.getTopRight() - borderWidth.top, 0), + Math.max(borderRadius.getBottomRight() - borderWidth.right, 0), + Math.max(borderRadius.getBottomRight() - borderWidth.bottom, 0), + Math.max(borderRadius.getBottomLeft() - borderWidth.left, 0), + Math.max(borderRadius.getBottomLeft() - borderWidth.bottom, 0), }, Path.Direction.CW); canvas.clipPath(mPath);