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
This commit is contained in:
Nick Gerleman
2024-04-13 15:34:12 -07:00
committed by Facebook GitHub Bot
parent efc5ddd3ee
commit c710386e72
6 changed files with 314 additions and 270 deletions
@@ -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 <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> (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 <init> (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 <init> ()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
@@ -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)
}
}
@@ -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);
@@ -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<Pair<BorderRadiusProp, Float>>) : 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")
}
}
}
@@ -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
}
}
@@ -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);