Add percentages for border-radius props (#44408)

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

Why?
Previously we didn't support using percentages like:
```
style={{
  width=100,
  height=100,
  borderRadius='100%',
}}
```

These percentages refer to the corresponding dimension of the border box.

What?
- Added LengthPercentage class and LengthPercentageType enum. To track when we are dealing with percentage vs points
- Now radius properties start as Dynamic which then get transformed into LengthPercentage.
- Modified certain function parameters so we can consider height and width when resolving BorderRadius values

With this we conditionally calculate the corresponding point (dp) value for a given percentage (considering size). Ex:

```
result = {raw_percentage_value} / 100 * (max(height, width))
```

We know the maximum border radius for our current implementation is half the dp of the shorter side of our view, hence why we consider half our maximum view side as equivalent to 100%.

Note: We still don't support vertical/horizontal border radii

## Changelog:

[Android][Added] - Added support for using percentages when defining border radius related properties.

Reviewed By: NickGerleman

Differential Revision: D56943825

fbshipit-source-id: 3e5a9933ca90e499aff9c7d2561f5f6bb55157da
This commit is contained in:
Jorge Cabiedes Acosta
2024-05-09 11:18:27 -07:00
committed by Facebook GitHub Bot
parent a45c5895f9
commit 181ed33ab0
7 changed files with 270 additions and 135 deletions
@@ -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 <init> ()V
public fun <init> (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 <init> ()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 <init> ()V
public fun <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> ()V
public fun <init> (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
@@ -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)
}
@@ -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);
@@ -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<Pair<BorderRadiusProp, Float>>) : this() {
public constructor(properties: List<Pair<BorderRadiusProp, LengthPercentage>>) : 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")
}
@@ -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)
}
@@ -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) {
@@ -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<ReactViewGroup> {
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")