in ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java [475:876]
private void updatePath() {
if (!mNeedUpdatePathForBorderRadius) {
return;
}
mNeedUpdatePathForBorderRadius = false;
if (mInnerClipPathForBorderRadius == null) {
mInnerClipPathForBorderRadius = new Path();
}
if (mOuterClipPathForBorderRadius == null) {
mOuterClipPathForBorderRadius = new Path();
}
if (mPathForBorderRadiusOutline == null) {
mPathForBorderRadiusOutline = new Path();
}
if (mCenterDrawPath == null) {
mCenterDrawPath = new Path();
}
if (mInnerClipTempRectForBorderRadius == null) {
mInnerClipTempRectForBorderRadius = new RectF();
}
if (mOuterClipTempRectForBorderRadius == null) {
mOuterClipTempRectForBorderRadius = new RectF();
}
if (mTempRectForBorderRadiusOutline == null) {
mTempRectForBorderRadiusOutline = new RectF();
}
if (mTempRectForCenterDrawPath == null) {
mTempRectForCenterDrawPath = new RectF();
}
mInnerClipPathForBorderRadius.reset();
mOuterClipPathForBorderRadius.reset();
mPathForBorderRadiusOutline.reset();
mCenterDrawPath.reset();
mInnerClipTempRectForBorderRadius.set(getBounds());
mOuterClipTempRectForBorderRadius.set(getBounds());
mTempRectForBorderRadiusOutline.set(getBounds());
mTempRectForCenterDrawPath.set(getBounds());
final RectF borderWidth = getDirectionAwareBorderInsets();
mInnerClipTempRectForBorderRadius.top += borderWidth.top;
mInnerClipTempRectForBorderRadius.bottom -= borderWidth.bottom;
mInnerClipTempRectForBorderRadius.left += borderWidth.left;
mInnerClipTempRectForBorderRadius.right -= borderWidth.right;
mTempRectForCenterDrawPath.top += borderWidth.top * 0.5f;
mTempRectForCenterDrawPath.bottom -= borderWidth.bottom * 0.5f;
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);
if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) {
if (YogaConstants.isUndefined(topStartRadius)) {
topStartRadius = topLeftRadius;
}
if (YogaConstants.isUndefined(topEndRadius)) {
topEndRadius = topRightRadius;
}
if (YogaConstants.isUndefined(bottomStartRadius)) {
bottomStartRadius = bottomLeftRadius;
}
if (YogaConstants.isUndefined(bottomEndRadius)) {
bottomEndRadius = bottomRightRadius;
}
final float directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
final float directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
final float directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
final float directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;
topLeftRadius = directionAwareTopLeftRadius;
topRightRadius = directionAwareTopRightRadius;
bottomLeftRadius = directionAwareBottomLeftRadius;
bottomRightRadius = directionAwareBottomRightRadius;
} else {
final float directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
final float directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
final float directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
final float directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;
if (!YogaConstants.isUndefined(directionAwareTopLeftRadius)) {
topLeftRadius = directionAwareTopLeftRadius;
}
if (!YogaConstants.isUndefined(directionAwareTopRightRadius)) {
topRightRadius = directionAwareTopRightRadius;
}
if (!YogaConstants.isUndefined(directionAwareBottomLeftRadius)) {
bottomLeftRadius = directionAwareBottomLeftRadius;
}
if (!YogaConstants.isUndefined(directionAwareBottomRightRadius)) {
bottomRightRadius = directionAwareBottomRightRadius;
}
}
final float innerTopLeftRadiusX = Math.max(topLeftRadius - borderWidth.left, 0);
final float innerTopLeftRadiusY = Math.max(topLeftRadius - borderWidth.top, 0);
final float innerTopRightRadiusX = Math.max(topRightRadius - borderWidth.right, 0);
final float innerTopRightRadiusY = Math.max(topRightRadius - borderWidth.top, 0);
final float innerBottomRightRadiusX = Math.max(bottomRightRadius - borderWidth.right, 0);
final float innerBottomRightRadiusY = Math.max(bottomRightRadius - borderWidth.bottom, 0);
final float innerBottomLeftRadiusX = Math.max(bottomLeftRadius - borderWidth.left, 0);
final float innerBottomLeftRadiusY = Math.max(bottomLeftRadius - borderWidth.bottom, 0);
mInnerClipPathForBorderRadius.addRoundRect(
mInnerClipTempRectForBorderRadius,
new float[] {
innerTopLeftRadiusX,
innerTopLeftRadiusY,
innerTopRightRadiusX,
innerTopRightRadiusY,
innerBottomRightRadiusX,
innerBottomRightRadiusY,
innerBottomLeftRadiusX,
innerBottomLeftRadiusY,
},
Path.Direction.CW);
mOuterClipPathForBorderRadius.addRoundRect(
mOuterClipTempRectForBorderRadius,
new float[] {
topLeftRadius,
topLeftRadius,
topRightRadius,
topRightRadius,
bottomRightRadius,
bottomRightRadius,
bottomLeftRadius,
bottomLeftRadius
},
Path.Direction.CW);
float extraRadiusForOutline = 0;
if (mBorderWidth != null) {
extraRadiusForOutline = mBorderWidth.get(Spacing.ALL) / 2f;
}
mPathForBorderRadiusOutline.addRoundRect(
mTempRectForBorderRadiusOutline,
new float[] {
topLeftRadius + extraRadiusForOutline,
topLeftRadius + extraRadiusForOutline,
topRightRadius + extraRadiusForOutline,
topRightRadius + extraRadiusForOutline,
bottomRightRadius + extraRadiusForOutline,
bottomRightRadius + extraRadiusForOutline,
bottomLeftRadius + extraRadiusForOutline,
bottomLeftRadius + extraRadiusForOutline
},
Path.Direction.CW);
mCenterDrawPath.addRoundRect(
mTempRectForCenterDrawPath,
new float[] {
Math.max(
topLeftRadius - borderWidth.left * 0.5f,
(borderWidth.left > 0.0f) ? (topLeftRadius / borderWidth.left) : 0.0f),
Math.max(
topLeftRadius - borderWidth.top * 0.5f,
(borderWidth.top > 0.0f) ? (topLeftRadius / borderWidth.top) : 0.0f),
Math.max(
topRightRadius - borderWidth.right * 0.5f,
(borderWidth.right > 0.0f) ? (topRightRadius / borderWidth.right) : 0.0f),
Math.max(
topRightRadius - borderWidth.top * 0.5f,
(borderWidth.top > 0.0f) ? (topRightRadius / borderWidth.top) : 0.0f),
Math.max(
bottomRightRadius - borderWidth.right * 0.5f,
(borderWidth.right > 0.0f) ? (bottomRightRadius / borderWidth.right) : 0.0f),
Math.max(
bottomRightRadius - borderWidth.bottom * 0.5f,
(borderWidth.bottom > 0.0f) ? (bottomRightRadius / borderWidth.bottom) : 0.0f),
Math.max(
bottomLeftRadius - borderWidth.left * 0.5f,
(borderWidth.left > 0.0f) ? (bottomLeftRadius / borderWidth.left) : 0.0f),
Math.max(
bottomLeftRadius - borderWidth.bottom * 0.5f,
(borderWidth.bottom > 0.0f) ? (bottomLeftRadius / borderWidth.bottom) : 0.0f)
},
Path.Direction.CW);
/**
* Rounded Multi-Colored Border Algorithm:
*
* <p>Let O (for outer) = (top, left, bottom, right) be the rectangle that represents the size
* and position of a view V. Since the box-sizing of all React Native views is border-box, any
* border of V will render inside O.
*
* <p>Let BorderWidth = (borderTop, borderLeft, borderBottom, borderRight).
*
* <p>Let I (for inner) = O - BorderWidth.
*
* <p>Then, remembering that O and I are rectangles and that I is inside O, O - I gives us the
* border of V. Therefore, we can use canvas.clipPath to draw V's border.
*
* <p>canvas.clipPath(O, Region.OP.INTERSECT);
*
* <p>canvas.clipPath(I, Region.OP.DIFFERENCE);
*
* <p>canvas.drawRect(O, paint);
*
* <p>This lets us draw non-rounded single-color borders.
*
* <p>To extend this algorithm to rounded single-color borders, we:
*
* <p>1. Curve the corners of O by the (border radii of V) using Path#addRoundRect.
*
* <p>2. Curve the corners of I by (border radii of V - border widths of V) using
* Path#addRoundRect.
*
* <p>Let O' = curve(O, border radii of V).
*
* <p>Let I' = curve(I, border radii of V - border widths of V)
*
* <p>The rationale behind this decision is the (first sentence of the) following section in the
* CSS Backgrounds and Borders Module Level 3:
* https://www.w3.org/TR/css3-background/#the-border-radius.
*
* <p>After both O and I have been curved, we can execute the following lines once again to
* render curved single-color borders:
*
* <p>canvas.clipPath(O, Region.OP.INTERSECT);
*
* <p>canvas.clipPath(I, Region.OP.DIFFERENCE);
*
* <p>canvas.drawRect(O, paint);
*
* <p>To extend this algorithm to rendering multi-colored rounded borders, we render each side
* of the border as its own quadrilateral. Suppose that we were handling the case where all the
* border radii are 0. Then, the four quadrilaterals would be:
*
* <p>Left: (O.left, O.top), (I.left, I.top), (I.left, I.bottom), (O.left, O.bottom)
*
* <p>Top: (O.left, O.top), (I.left, I.top), (I.right, I.top), (O.right, O.top)
*
* <p>Right: (O.right, O.top), (I.right, I.top), (I.right, I.bottom), (O.right, O.bottom)
*
* <p>Bottom: (O.right, O.bottom), (I.right, I.bottom), (I.left, I.bottom), (O.left, O.bottom)
*
* <p>Now, lets consider what happens when we render a rounded border (radii != 0). For the sake
* of simplicity, let's focus on the top edge of the Left border:
*
* <p>Let borderTopLeftRadius = 5. Let borderLeftWidth = 1. Let borderTopWidth = 2.
*
* <p>We know that O is curved by the ellipse E_O (a = 5, b = 5). We know that I is curved by
* the ellipse E_I (a = 5 - 1, b = 5 - 2).
*
* <p>Since we have clipping, it should be safe to set the top-left point of the Left
* quadrilateral's top edge to (O.left, O.top).
*
* <p>But, what should the top-right point be?
*
* <p>The fact that the border is curved shouldn't change the slope (nor the position) of the
* line connecting the top-left and top-right points of the Left quadrilateral's top edge.
* Therefore, The top-right point should lie somewhere on the line L = (1 - a) * (O.left, O.top)
* + a * (I.left, I.top).
*
* <p>a != 0, because then the top-left and top-right points would be the same and
* borderLeftWidth = 1. a != 1, because then the top-right point would not touch an edge of the
* ellipse E_I. We want the top-right point to touch an edge of the inner ellipse because the
* border curves with E_I on the top-left corner of V.
*
* <p>Therefore, it must be the case that a > 1. Two natural locations of the top-right point
* exist: 1. The first intersection of L with E_I. 2. The second intersection of L with E_I.
*
* <p>We choose the top-right point of the top edge of the Left quadrilateral to be an arbitrary
* intersection of L with E_I.
*/
if (mInnerTopLeftCorner == null) {
mInnerTopLeftCorner = new PointF();
}
/** Compute mInnerTopLeftCorner */
mInnerTopLeftCorner.x = mInnerClipTempRectForBorderRadius.left;
mInnerTopLeftCorner.y = mInnerClipTempRectForBorderRadius.top;
getEllipseIntersectionWithLine(
// Ellipse Bounds
mInnerClipTempRectForBorderRadius.left,
mInnerClipTempRectForBorderRadius.top,
mInnerClipTempRectForBorderRadius.left + 2 * innerTopLeftRadiusX,
mInnerClipTempRectForBorderRadius.top + 2 * innerTopLeftRadiusY,
// Line Start
mOuterClipTempRectForBorderRadius.left,
mOuterClipTempRectForBorderRadius.top,
// Line End
mInnerClipTempRectForBorderRadius.left,
mInnerClipTempRectForBorderRadius.top,
// Result
mInnerTopLeftCorner);
/** Compute mInnerBottomLeftCorner */
if (mInnerBottomLeftCorner == null) {
mInnerBottomLeftCorner = new PointF();
}
mInnerBottomLeftCorner.x = mInnerClipTempRectForBorderRadius.left;
mInnerBottomLeftCorner.y = mInnerClipTempRectForBorderRadius.bottom;
getEllipseIntersectionWithLine(
// Ellipse Bounds
mInnerClipTempRectForBorderRadius.left,
mInnerClipTempRectForBorderRadius.bottom - 2 * innerBottomLeftRadiusY,
mInnerClipTempRectForBorderRadius.left + 2 * innerBottomLeftRadiusX,
mInnerClipTempRectForBorderRadius.bottom,
// Line Start
mOuterClipTempRectForBorderRadius.left,
mOuterClipTempRectForBorderRadius.bottom,
// Line End
mInnerClipTempRectForBorderRadius.left,
mInnerClipTempRectForBorderRadius.bottom,
// Result
mInnerBottomLeftCorner);
/** Compute mInnerTopRightCorner */
if (mInnerTopRightCorner == null) {
mInnerTopRightCorner = new PointF();
}
mInnerTopRightCorner.x = mInnerClipTempRectForBorderRadius.right;
mInnerTopRightCorner.y = mInnerClipTempRectForBorderRadius.top;
getEllipseIntersectionWithLine(
// Ellipse Bounds
mInnerClipTempRectForBorderRadius.right - 2 * innerTopRightRadiusX,
mInnerClipTempRectForBorderRadius.top,
mInnerClipTempRectForBorderRadius.right,
mInnerClipTempRectForBorderRadius.top + 2 * innerTopRightRadiusY,
// Line Start
mOuterClipTempRectForBorderRadius.right,
mOuterClipTempRectForBorderRadius.top,
// Line End
mInnerClipTempRectForBorderRadius.right,
mInnerClipTempRectForBorderRadius.top,
// Result
mInnerTopRightCorner);
/** Compute mInnerBottomRightCorner */
if (mInnerBottomRightCorner == null) {
mInnerBottomRightCorner = new PointF();
}
mInnerBottomRightCorner.x = mInnerClipTempRectForBorderRadius.right;
mInnerBottomRightCorner.y = mInnerClipTempRectForBorderRadius.bottom;
getEllipseIntersectionWithLine(
// Ellipse Bounds
mInnerClipTempRectForBorderRadius.right - 2 * innerBottomRightRadiusX,
mInnerClipTempRectForBorderRadius.bottom - 2 * innerBottomRightRadiusY,
mInnerClipTempRectForBorderRadius.right,
mInnerClipTempRectForBorderRadius.bottom,
// Line Start
mOuterClipTempRectForBorderRadius.right,
mOuterClipTempRectForBorderRadius.bottom,
// Line End
mInnerClipTempRectForBorderRadius.right,
mInnerClipTempRectForBorderRadius.bottom,
// Result
mInnerBottomRightCorner);
}