diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 2d644928821..1043824e561 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -4188,6 +4188,25 @@ public class com/facebook/react/uimanager/LayoutShadowNode$$PropsSetter : com/fa public synthetic fun setProperty (Lcom/facebook/react/uimanager/ReactShadowNode;Ljava/lang/String;Ljava/lang/Object;)V } +public final class com/facebook/react/uimanager/LengthPercentage { + public static final field Companion Lcom/facebook/react/uimanager/LengthPercentage$Companion; + public fun ()V + public fun (FLcom/facebook/react/uimanager/LengthPercentageType;)V + public final fun resolve (FF)F + public static final fun setFromDynamic (Lcom/facebook/react/bridge/Dynamic;)Lcom/facebook/react/uimanager/LengthPercentage; +} + +public final class com/facebook/react/uimanager/LengthPercentage$Companion { + public final fun setFromDynamic (Lcom/facebook/react/bridge/Dynamic;)Lcom/facebook/react/uimanager/LengthPercentage; +} + +public final class com/facebook/react/uimanager/LengthPercentageType : java/lang/Enum { + public static final field PERCENT Lcom/facebook/react/uimanager/LengthPercentageType; + public static final field POINT Lcom/facebook/react/uimanager/LengthPercentageType; + public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/LengthPercentageType; + public static fun values ()[Lcom/facebook/react/uimanager/LengthPercentageType; +} + public class com/facebook/react/uimanager/MatrixMathHelper { public fun ()V public static fun applyPerspective ([DD)V @@ -5483,6 +5502,7 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro public fun getBorderColor (I)I public fun getBorderRadius ()Lcom/facebook/react/uimanager/style/BorderRadiusStyle; public fun getBorderWidthOrDefaultTo (FI)F + public fun getComputedBorderRadius ()Lcom/facebook/react/uimanager/style/ComputedBorderRadius; public fun getDirectionAwareBorderInsets ()Landroid/graphics/RectF; public fun getFullBorderWidth ()F public fun getOpacity ()I @@ -5494,7 +5514,7 @@ 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/BorderRadiusProp;Lcom/facebook/react/uimanager/LengthPercentage;)V public fun setBorderRadius (Lcom/facebook/react/uimanager/style/BorderRadiusStyle;)V public fun setBorderStyle (Ljava/lang/String;)V public fun setBorderWidth (IF)V @@ -5819,60 +5839,61 @@ public final class com/facebook/react/uimanager/style/BorderRadiusProp : java/la 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 (Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;)V + public synthetic fun (Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;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 final fun component1 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component10 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component11 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component12 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component13 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component2 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component3 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component4 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component5 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component6 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component7 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component8 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun component9 ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun copy (Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;)Lcom/facebook/react/uimanager/style/BorderRadiusStyle; + public static synthetic fun copy$default (Lcom/facebook/react/uimanager/style/BorderRadiusStyle;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;Lcom/facebook/react/uimanager/LengthPercentage;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 get (Lcom/facebook/react/uimanager/style/BorderRadiusProp;)Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getBottomEnd ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getBottomLeft ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getBottomRight ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getBottomStart ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getEndEnd ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getEndStart ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getStartEnd ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getStartStart ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getTopEnd ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getTopLeft ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getTopRight ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getTopStart ()Lcom/facebook/react/uimanager/LengthPercentage; + public final fun getUniform ()Lcom/facebook/react/uimanager/LengthPercentage; 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 final fun resolve (ILandroid/content/Context;FF)Lcom/facebook/react/uimanager/style/ComputedBorderRadius; + public final fun set (Lcom/facebook/react/uimanager/style/BorderRadiusProp;Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setBottomEnd (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setBottomLeft (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setBottomRight (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setBottomStart (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setEndEnd (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setEndStart (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setStartEnd (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setStartStart (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setTopEnd (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setTopLeft (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setTopRight (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setTopStart (Lcom/facebook/react/uimanager/LengthPercentage;)V + public final fun setUniform (Lcom/facebook/react/uimanager/LengthPercentage;)V public fun toString ()Ljava/lang/String; } public final class com/facebook/react/uimanager/style/ComputedBorderRadius { + public fun ()V public fun (FFFF)V public final fun component1 ()F public final fun component2 ()F @@ -7904,7 +7925,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 setBorderRadius (Lcom/facebook/react/uimanager/style/BorderRadiusProp;Lcom/facebook/react/uimanager/LengthPercentage;)V public fun setBorderStyle (Ljava/lang/String;)V public fun setBorderWidth (IF)V public fun setHitSlopRect (Landroid/graphics/Rect;)V @@ -7940,6 +7961,7 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react public fun setBackfaceVisibility (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V public fun setBorderColor (Lcom/facebook/react/views/view/ReactViewGroup;ILjava/lang/Integer;)V public fun setBorderRadius (Lcom/facebook/react/views/view/ReactViewGroup;IF)V + public fun setBorderRadius (Lcom/facebook/react/views/view/ReactViewGroup;ILcom/facebook/react/bridge/Dynamic;)V public fun setBorderStyle (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V public fun setBorderWidth (Lcom/facebook/react/views/view/ReactViewGroup;IF)V public fun setCollapsable (Lcom/facebook/react/views/view/ReactViewGroup;Z)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LengthPercentage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LengthPercentage.kt new file mode 100644 index 00000000000..2616b65c50f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LengthPercentage.kt @@ -0,0 +1,73 @@ +/* + * 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 + +import com.facebook.common.logging.FLog +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.ReadableType +import com.facebook.react.common.ReactConstants +import java.lang.NumberFormatException + +public enum class LengthPercentageType { + POINT, + PERCENT, +} + +public class LengthPercentage( + private val value: Float, + private val unit: LengthPercentageType, +) { + public companion object { + @JvmStatic + public fun setFromDynamic(dynamic: Dynamic): LengthPercentage? { + return when (dynamic.getType()) { + ReadableType.Number -> { + val value = dynamic.asDouble() + if (value > 0f) { + LengthPercentage(PixelUtil.toPixelFromDIP(value), LengthPercentageType.POINT) + } else { + null + } + } + ReadableType.String -> { + val s = dynamic.asString() + if (s.endsWith("%")) { + try { + val value = s.substring(0, s.length - 1).toFloat() + if (value > 0f) { + LengthPercentage(value, LengthPercentageType.PERCENT) + } else { + null + } + } catch (e: NumberFormatException) { + FLog.w(ReactConstants.TAG, "Invalid percentage format: $s") + null + } + } else { + FLog.w(ReactConstants.TAG, "Invalid string value: $s") + null + } + } + else -> { + FLog.w(ReactConstants.TAG, "Unsupported type for radius property: ${dynamic.getType()}") + null + } + } + } + } + + public fun resolve(width: Float, height: Float): Float { + if (unit == LengthPercentageType.PERCENT) { + return (value / 100) * Math.max(width, height) + } + + return value + } + + public constructor() : this(0f, LengthPercentageType.POINT) +} 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 21094fc73d5..4e795bf6f23 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 @@ -27,11 +27,14 @@ import androidx.core.graphics.ColorUtils; 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.LengthPercentage; +import com.facebook.react.uimanager.LengthPercentageType; import com.facebook.react.uimanager.Spacing; 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; +import java.util.Objects; /** * A subclass of {@link Drawable} used for background of {@link @@ -114,6 +117,7 @@ public class CSSBackgroundDrawable extends Drawable { private final float mGapBetweenPaths = 0.8f; private BorderRadiusStyle mBorderRadius = new BorderRadiusStyle(); + private ComputedBorderRadius mComputedBorderRadius = new ComputedBorderRadius(); private final Context mContext; private int mLayoutDirection; @@ -235,23 +239,36 @@ public class CSSBackgroundDrawable extends Drawable { } /** - * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead. + * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, LengthPercentage)} instead. */ public void setRadius(float radius) { @Nullable Float boxedRadius = Float.isNaN(radius) ? null : Float.valueOf(radius); - setBorderRadius(BorderRadiusProp.BORDER_RADIUS, boxedRadius); + if (boxedRadius == null) { + setBorderRadius(BorderRadiusProp.BORDER_RADIUS, null); + } else { + setBorderRadius( + BorderRadiusProp.BORDER_RADIUS, + new LengthPercentage(boxedRadius, LengthPercentageType.POINT)); + } } /** - * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead. + * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, LengthPercentage)} instead. */ public void setRadius(float radius, int position) { @Nullable Float boxedRadius = Float.isNaN(radius) ? null : Float.valueOf(radius); - setBorderRadius(BorderRadiusProp.values()[position], boxedRadius); + + if (boxedRadius == null) { + mBorderRadius.set(BorderRadiusProp.values()[position], null); + } else { + setBorderRadius( + BorderRadiusProp.values()[position], + new LengthPercentage(boxedRadius, LengthPercentageType.POINT)); + } } - public void setBorderRadius(BorderRadiusProp property, @Nullable Float radius) { - if (!FloatUtil.floatsEqual(mBorderRadius.getUniform(), radius)) { + public void setBorderRadius(BorderRadiusProp property, @Nullable LengthPercentage radius) { + if (!Objects.equals(radius, mBorderRadius.get(property))) { mBorderRadius.set(property, radius); mNeedUpdatePathForBorderRadius = true; invalidateSelf(); @@ -266,6 +283,10 @@ public class CSSBackgroundDrawable extends Drawable { return mBorderRadius; } + public ComputedBorderRadius getComputedBorderRadius() { + return mComputedBorderRadius; + } + public void setColor(int color) { mColor = color; invalidateSelf(); @@ -568,11 +589,16 @@ public class CSSBackgroundDrawable extends Drawable { mTempRectForCenterDrawPath.left += borderWidth.left * 0.5f; mTempRectForCenterDrawPath.right -= borderWidth.right * 0.5f; - ComputedBorderRadius radius = mBorderRadius.resolve(mLayoutDirection, mContext); - float topLeftRadius = radius.getTopLeft(); - float topRightRadius = radius.getTopRight(); - float bottomLeftRadius = radius.getBottomLeft(); - float bottomRightRadius = radius.getBottomRight(); + mComputedBorderRadius = + mBorderRadius.resolve( + mLayoutDirection, + mContext, + mOuterClipTempRectForBorderRadius.width(), + mOuterClipTempRectForBorderRadius.height()); + float topLeftRadius = mComputedBorderRadius.getTopLeft(); + float topRightRadius = mComputedBorderRadius.getTopRight(); + 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); 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 index 0683b01b073..0f475b4a327 100644 --- 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 @@ -10,9 +10,11 @@ package com.facebook.react.uimanager.style import android.content.Context import android.util.LayoutDirection import com.facebook.react.modules.i18nmanager.I18nUtil +import com.facebook.react.uimanager.LengthPercentage /** Represents the collection of possible border radius style properties. */ public enum class BorderRadiusProp { + BORDER_RADIUS, BORDER_TOP_LEFT_RADIUS, BORDER_TOP_RIGHT_RADIUS, BORDER_BOTTOM_RIGHT_RADIUS, @@ -25,30 +27,29 @@ public enum class BorderRadiusProp { 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 + var uniform: LengthPercentage? = null, + var topLeft: LengthPercentage? = null, + var topRight: LengthPercentage? = null, + var bottomLeft: LengthPercentage? = null, + var bottomRight: LengthPercentage? = null, + var topStart: LengthPercentage? = null, + var topEnd: LengthPercentage? = null, + var bottomStart: LengthPercentage? = null, + var bottomEnd: LengthPercentage? = null, + var startStart: LengthPercentage? = null, + var startEnd: LengthPercentage? = null, + var endStart: LengthPercentage? = null, + var endEnd: LengthPercentage? = null ) { - public constructor(properties: List>) : this() { + public constructor(properties: List>) : this() { properties.forEach { (k, v) -> set(k, v) } } - public fun set(property: BorderRadiusProp, value: Float?) { + public fun set(property: BorderRadiusProp, value: LengthPercentage?) { when (property) { BorderRadiusProp.BORDER_RADIUS -> uniform = value BorderRadiusProp.BORDER_TOP_LEFT_RADIUS -> topLeft = value @@ -66,7 +67,7 @@ public data class BorderRadiusStyle( } } - public fun get(property: BorderRadiusProp): Float? { + public fun get(property: BorderRadiusProp): LengthPercentage? { return when (property) { BorderRadiusProp.BORDER_RADIUS -> uniform BorderRadiusProp.BORDER_TOP_LEFT_RADIUS -> topLeft @@ -85,45 +86,55 @@ public data class BorderRadiusStyle( } 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) + return uniform != null || + topLeft != null || + topRight != null || + bottomLeft != null || + bottomRight != null || + topStart != null || + topEnd != null || + bottomStart != null || + bottomEnd != null || + startStart != null || + startEnd != null || + endStart != null || + endEnd != null } public fun resolve( layoutDirection: Int, context: Context, + width: Float, + height: Float, ): ComputedBorderRadius { + val topLeft: LengthPercentage? = startStart ?: topStart ?: topLeft ?: uniform + val topRight: LengthPercentage? = endStart ?: topEnd ?: topRight ?: uniform + val bottomLeft: LengthPercentage? = startEnd ?: bottomStart ?: bottomLeft ?: uniform + val bottomRight: LengthPercentage? = endEnd ?: bottomEnd ?: bottomRight ?: uniform + 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) + topLeft = topLeft?.resolve(width, height) ?: 0f, + topRight = topRight?.resolve(width, height) ?: 0f, + bottomLeft = bottomLeft?.resolve(width, height) ?: 0f, + bottomRight = bottomRight?.resolve(width, height) ?: 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) + topLeft = topRight?.resolve(width, height) ?: 0f, + topRight = topLeft?.resolve(width, height) ?: 0f, + bottomLeft = bottomRight?.resolve(width, height) ?: 0f, + bottomRight = bottomLeft?.resolve(width, height) ?: 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) + topLeft = topRight?.resolve(width, height) ?: 0f, + topRight = topLeft?.resolve(width, height) ?: 0f, + bottomLeft = bottomRight?.resolve(width, height) ?: 0f, + bottomRight = bottomLeft?.resolve(width, height) ?: 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 index 181bc4ecba0..d337e80ba35 100644 --- 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 @@ -17,4 +17,6 @@ public data class ComputedBorderRadius( public fun hasRoundedBorders(): Boolean { return topLeft > 0f || topRight > 0f || bottomLeft > 0f || bottomRight > 0f } + + public constructor() : this(0f, 0f, 0f, 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 ee31c48404b..ce066636ca8 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 @@ -37,6 +37,7 @@ import com.facebook.react.touch.OnInterceptTouchEventListener; import com.facebook.react.touch.ReactHitSlopView; import com.facebook.react.touch.ReactInterceptingViewGroup; import com.facebook.react.uimanager.IllegalViewOperationException; +import com.facebook.react.uimanager.LengthPercentage; import com.facebook.react.uimanager.MeasureSpecAssertions; import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.ReactClippingProhibitedView; @@ -123,7 +124,7 @@ public class ReactViewGroup extends ViewGroup private @Nullable String mOverflow; private PointerEvents mPointerEvents; private @Nullable ChildrenLayoutChangeListener mChildrenLayoutChangeListener; - private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable; + private @Nullable CSSBackgroundDrawable mCSSBackgroundDrawable; private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener; private boolean mNeedsOffscreenAlphaCompositing; private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper; @@ -153,7 +154,7 @@ public class ReactViewGroup extends ViewGroup mOverflow = null; mPointerEvents = PointerEvents.AUTO; mChildrenLayoutChangeListener = null; - mReactBackgroundDrawable = null; + mCSSBackgroundDrawable = null; mOnInterceptTouchEventListener = null; mNeedsOffscreenAlphaCompositing = false; mDrawingOrderHelper = null; @@ -200,8 +201,8 @@ public class ReactViewGroup extends ViewGroup @Override public void onRtlPropertiesChanged(int layoutDirection) { - if (mReactBackgroundDrawable != null) { - mReactBackgroundDrawable.setResolvedLayoutDirection(mLayoutDirection); + if (mCSSBackgroundDrawable != null) { + mCSSBackgroundDrawable.setResolvedLayoutDirection(mLayoutDirection); } } @@ -224,7 +225,7 @@ public class ReactViewGroup extends ViewGroup @Override public void setBackgroundColor(int color) { - if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) { + if (color == Color.TRANSPARENT && mCSSBackgroundDrawable == null) { // don't do anything, no need to allocate ReactBackgroundDrawable for transparent background } else { getOrCreateReactViewBackground().setColor(color); @@ -243,9 +244,9 @@ public class ReactViewGroup extends ViewGroup // as a background previously. This will not work correctly as the drawable callback logic is // messed up in AOSP updateBackgroundDrawable(null); - if (mReactBackgroundDrawable != null && background != null) { + if (mCSSBackgroundDrawable != null && background != null) { LayerDrawable layerDrawable = - new LayerDrawable(new Drawable[] {mReactBackgroundDrawable, background}); + new LayerDrawable(new Drawable[] {mCSSBackgroundDrawable, background}); updateBackgroundDrawable(layerDrawable); } else if (background != null) { updateBackgroundDrawable(background); @@ -332,7 +333,7 @@ public class ReactViewGroup extends ViewGroup backgroundDrawable.setRadius(borderRadius, position); } - public void setBorderRadius(BorderRadiusProp property, @Nullable Float borderRadius) { + public void setBorderRadius(BorderRadiusProp property, @Nullable LengthPercentage borderRadius) { CSSBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground(); backgroundDrawable.setBorderRadius(property, borderRadius); } @@ -791,26 +792,26 @@ public class ReactViewGroup extends ViewGroup return DEFAULT_BACKGROUND_COLOR; } - /* package */ ReactViewBackgroundDrawable getOrCreateReactViewBackground() { - if (mReactBackgroundDrawable == null) { - mReactBackgroundDrawable = new ReactViewBackgroundDrawable(getContext()); + /* package */ CSSBackgroundDrawable getOrCreateReactViewBackground() { + if (mCSSBackgroundDrawable == null) { + mCSSBackgroundDrawable = new CSSBackgroundDrawable(getContext()); Drawable backgroundDrawable = getBackground(); updateBackgroundDrawable( null); // required so that drawable callback is cleared before we add the // drawable back as a part of LayerDrawable if (backgroundDrawable == null) { - updateBackgroundDrawable(mReactBackgroundDrawable); + updateBackgroundDrawable(mCSSBackgroundDrawable); } else { LayerDrawable layerDrawable = - new LayerDrawable(new Drawable[] {mReactBackgroundDrawable, backgroundDrawable}); + new LayerDrawable(new Drawable[] {mCSSBackgroundDrawable, backgroundDrawable}); updateBackgroundDrawable(layerDrawable); } mLayoutDirection = I18nUtil.getInstance().isRTL(getContext()) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR; - mReactBackgroundDrawable.setResolvedLayoutDirection(mLayoutDirection); + mCSSBackgroundDrawable.setResolvedLayoutDirection(mLayoutDirection); } - return mReactBackgroundDrawable; + return mCSSBackgroundDrawable; } @Override @@ -849,7 +850,7 @@ public class ReactViewGroup extends ViewGroup * @param drawable {@link Drawable} The Drawable to use as the background, or null to remove the * background */ - /* package */ void updateBackgroundDrawable(Drawable drawable) { + /* package */ void updateBackgroundDrawable(@Nullable Drawable drawable) { super.setBackground(drawable); } @@ -909,8 +910,8 @@ public class ReactViewGroup extends ViewGroup boolean hasClipPath = false; - if (mReactBackgroundDrawable != null) { - final RectF borderWidth = mReactBackgroundDrawable.getDirectionAwareBorderInsets(); + if (mCSSBackgroundDrawable != null) { + final RectF borderWidth = mCSSBackgroundDrawable.getDirectionAwareBorderInsets(); if (borderWidth.top > 0 || borderWidth.left > 0 @@ -923,7 +924,7 @@ public class ReactViewGroup extends ViewGroup } final ComputedBorderRadius borderRadius = - mReactBackgroundDrawable.getBorderRadius().resolve(mLayoutDirection, getContext()); + mCSSBackgroundDrawable.getComputedBorderRadius(); if (borderRadius.hasRoundedBorders()) { if (mPath == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 3d4b23416a2..df4b9c85ce4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.DynamicFromObject; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; @@ -21,6 +22,7 @@ import com.facebook.react.common.MapBuilder; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.LengthPercentage; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.Spacing; @@ -30,6 +32,7 @@ import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.style.BorderRadiusProp; import com.facebook.yoga.YogaConstants; import java.util.Map; @@ -126,22 +129,19 @@ public class ReactViewManager extends ReactClippingViewManager { ViewProps.BORDER_END_START_RADIUS, ViewProps.BORDER_START_END_RADIUS, ViewProps.BORDER_START_START_RADIUS, - }, - defaultFloat = YogaConstants.UNDEFINED) + }) + public void setBorderRadius(ReactViewGroup view, int index, Dynamic rawBorderRadius) { + + @Nullable LengthPercentage borderRadius = LengthPercentage.setFromDynamic(rawBorderRadius); + + view.setBorderRadius(BorderRadiusProp.values()[index], borderRadius); + } + + /** + * @deprecated Use {@link #setBorderRadius(ReactViewGroup, int, Dynamic)} instead. + */ public void setBorderRadius(ReactViewGroup view, int index, float borderRadius) { - if (!YogaConstants.isUndefined(borderRadius) && borderRadius < 0) { - borderRadius = YogaConstants.UNDEFINED; - } - - if (!YogaConstants.isUndefined(borderRadius)) { - borderRadius = PixelUtil.toPixelFromDIP(borderRadius); - } - - if (index == 0) { - view.setBorderRadius(borderRadius); - } else { - view.setBorderRadius(borderRadius, index - 1); - } + setBorderRadius(view, index, new DynamicFromObject(borderRadius)); } @ReactProp(name = "borderStyle")