Revert D57668976: Simplify ReactViewGroup clip to border

Differential Revision:
D57668976

Original commit changeset: 8b8cf956dc86

Original Phabricator Diff: D57668976

fbshipit-source-id: 5147557c1f3561863b1ad9226ca6244ff94cb29c
This commit is contained in:
Nick Gerleman
2024-05-24 11:55:43 -07:00
committed by Facebook GitHub Bot
parent ac51deeaf4
commit 91d12d9b99
5 changed files with 100 additions and 96 deletions
@@ -5557,21 +5557,21 @@ public abstract interface class com/facebook/react/uimanager/debug/NotThreadSafe
public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : android/graphics/drawable/Drawable {
public fun <init> (Landroid/content/Context;)V
public fun borderBoxPath ()Landroid/graphics/Path;
public fun draw (Landroid/graphics/Canvas;)V
public fun getAlpha ()I
public fun getBorderBoxPath ()Landroid/graphics/Path;
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 getLayoutDirection ()I
public fun getOpacity ()I
public fun getOutline (Landroid/graphics/Outline;)V
public fun getPaddingBoxPath ()Landroid/graphics/Path;
public fun getPaddingBoxRect ()Landroid/graphics/RectF;
public fun hasRoundedBorders ()Z
protected fun onBoundsChange (Landroid/graphics/Rect;)V
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;Lcom/facebook/react/uimanager/LengthPercentage;)V
@@ -81,7 +81,7 @@ internal class BoxShadowDrawable(
}
with(canvas) {
clipOutPath(background.getBorderBoxPath())
clipOutPath(background.borderBoxPath())
drawRenderNode(renderNode)
}
}
@@ -316,35 +316,22 @@ public class CSSBackgroundDrawable extends Drawable {
return mColor;
}
public Path getBorderBoxPath() {
public Path borderBoxPath() {
updatePath();
return Preconditions.checkNotNull(mOuterClipPathForBorderRadius);
}
public Path getPaddingBoxPath() {
public Path paddingBoxPath() {
updatePath();
return Preconditions.checkNotNull(mInnerClipPathForBorderRadius);
}
public RectF getPaddingBoxRect() {
RectF insets = getDirectionAwareBorderInsets();
if (insets == null) {
return new RectF(0, 0, getBounds().width(), getBounds().height());
}
return new RectF(
insets.left,
insets.top,
getBounds().width() - insets.right,
getBounds().height() - insets.bottom);
}
private void drawRoundedBackgroundWithBorders(Canvas canvas) {
updatePath();
canvas.save();
// Clip outer border
canvas.clipPath(getBorderBoxPath(), Region.Op.INTERSECT);
canvas.clipPath(borderBoxPath(), Region.Op.INTERSECT);
// Draws the View without its border first (with background color fill)
int useColor = ColorUtils.setAlphaComponent(mColor, getOpacity());
@@ -403,7 +390,7 @@ public class CSSBackgroundDrawable extends Drawable {
mPaint.setStyle(Paint.Style.FILL);
// Clip inner border
canvas.clipPath(getPaddingBoxPath(), Region.Op.DIFFERENCE);
canvas.clipPath(paddingBoxPath(), Region.Op.DIFFERENCE);
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
int colorStart = getBorderColor(Spacing.START);
@@ -1317,7 +1304,7 @@ public class CSSBackgroundDrawable extends Drawable {
return CSSBackgroundDrawable.colorFromAlphaAndRGBComponents(alpha, rgb);
}
private RectF getDirectionAwareBorderInsets() {
public RectF getDirectionAwareBorderInsets() {
final float borderWidth = getBorderWidthOrDefaultTo(0, Spacing.ALL);
final float borderTopWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP);
final float borderBottomWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM);
@@ -16,6 +16,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.MotionEvent;
@@ -26,6 +27,7 @@ import android.view.animation.Animation;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.UiThreadUtil;
@@ -35,6 +37,7 @@ import com.facebook.react.modules.i18nmanager.I18nUtil;
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;
@@ -44,13 +47,15 @@ import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactOverflowViewWithInset;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.ReactZIndexedViewGroup;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.RootViewUtil;
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.react.uimanager.drawable.CSSBackgroundDrawable;
import com.facebook.react.uimanager.style.BorderRadiusProp;
import java.util.Objects;
import com.facebook.react.uimanager.style.ComputedBorderRadius;
/**
* Backing for a React View. Has support for borders, but since borders aren't common, lazy
@@ -124,6 +129,7 @@ public class ReactViewGroup extends ViewGroup
private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
private boolean mNeedsOffscreenAlphaCompositing;
private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper;
private @Nullable Path mPath;
private float mBackfaceOpacity;
private String mBackfaceVisibility;
@@ -152,6 +158,7 @@ public class ReactViewGroup extends ViewGroup
mOnInterceptTouchEventListener = null;
mNeedsOffscreenAlphaCompositing = false;
mDrawingOrderHelper = null;
mPath = null;
mBackfaceOpacity = 1.f;
mBackfaceVisibility = "visible";
}
@@ -843,18 +850,25 @@ public class ReactViewGroup extends ViewGroup
@Override
protected void dispatchDraw(Canvas canvas) {
if (mCSSBackgroundDrawable != null
&& (Objects.equals(mOverflow, ViewProps.HIDDEN)
|| Objects.equals(mOverflow, ViewProps.SCROLL))) {
@Nullable Path paddingBoxPath = mCSSBackgroundDrawable.getPaddingBoxPath();
if (paddingBoxPath != null) {
canvas.clipPath(paddingBoxPath);
try {
dispatchOverflowDraw(canvas);
super.dispatchDraw(canvas);
} catch (NullPointerException | StackOverflowError e) {
// Adding special exception management for StackOverflowError for logging purposes.
// This will be removed in the future.
RootView rootView = RootViewUtil.getRootView(ReactViewGroup.this);
if (rootView != null) {
rootView.handleException(e);
} else {
canvas.clipRect(mCSSBackgroundDrawable.getPaddingBoxRect());
if (getContext() instanceof ReactContext) {
ReactContext reactContext = (ReactContext) getContext();
reactContext.handleException(
new IllegalViewOperationException("StackOverflowException", this, e));
} else {
throw e;
}
}
}
super.dispatchDraw(canvas);
}
@Override
@@ -873,6 +887,73 @@ public class ReactViewGroup extends ViewGroup
return result;
}
private void dispatchOverflowDraw(Canvas canvas) {
if (mOverflow != null) {
switch (mOverflow) {
case ViewProps.VISIBLE:
if (mPath != null) {
mPath.rewind();
}
break;
case ViewProps.HIDDEN:
case ViewProps.SCROLL:
float left = 0f;
float top = 0f;
float right = getWidth();
float bottom = getHeight();
boolean hasClipPath = false;
if (mCSSBackgroundDrawable != null) {
final RectF borderWidth = mCSSBackgroundDrawable.getDirectionAwareBorderInsets();
if (borderWidth.top > 0
|| borderWidth.left > 0
|| borderWidth.bottom > 0
|| borderWidth.right > 0) {
left += borderWidth.left;
top += borderWidth.top;
right -= borderWidth.right;
bottom -= borderWidth.bottom;
}
final ComputedBorderRadius borderRadius =
mCSSBackgroundDrawable.getComputedBorderRadius();
if (borderRadius.hasRoundedBorders()) {
if (mPath == null) {
mPath = new Path();
}
mPath.rewind();
mPath.addRoundRect(
new RectF(left, top, right, bottom),
new float[] {
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);
hasClipPath = true;
}
}
if (!hasClipPath) {
canvas.clipRect(new RectF(left, top, right, bottom));
}
break;
default:
break;
}
}
}
public void setOpacityIfPossible(float opacity) {
mBackfaceOpacity = opacity;
setBackfaceVisibilityDependantOpacity();
@@ -12,11 +12,9 @@
import type {RNTesterModule} from '../../types/RNTesterTypes';
import hotdog from '../../assets/hotdog.jpg';
import * as React from 'react';
import {
DynamicColorIOS,
Image,
Platform,
PlatformColor,
StyleSheet,
@@ -216,23 +214,6 @@ const styles = StyleSheet.create({
? DynamicColorIOS({light: 'magenta', dark: 'cyan'})
: 'black',
},
borderWithoutClipping: {
borderWidth: 10,
overflow: 'visible',
},
borderWithClipping: {
borderWidth: 10,
overflow: 'hidden',
},
borderWithClippingAndRadius: {
borderWidth: 10,
borderRadius: 30,
overflow: 'hidden',
},
hotdog: {
width: 100,
height: 100,
},
});
export default ({
@@ -496,50 +477,5 @@ export default ({
);
},
},
{
title: 'Child without clipping',
name: 'child-no-clipping',
description:
'"overflow: visible" will cause child content to show above borders',
render: function (): React.Node {
return (
<View
testID="border-test-child-no-clipping"
style={[styles.box, styles.borderWithoutClipping]}>
<Image source={hotdog} style={styles.hotdog} />
</View>
);
},
},
{
title: 'Child clipping',
name: 'child-clipping',
description:
'"overflow: hidden" will cause child content to be clipped to borders',
render: function (): React.Node {
return (
<View
testID="border-test-child-clipping"
style={[styles.box, styles.borderWithClipping]}>
<Image source={hotdog} style={styles.hotdog} />
</View>
);
},
},
{
title: 'Child clipping with radius',
name: 'child-clipping-radius',
description:
'"overflow: hidden" will cause child content to be clipped to rounded corners',
render: function (): React.Node {
return (
<View
testID="border-test-child-clipping-radius"
style={[styles.box, styles.borderWithClippingAndRadius]}>
<Image source={hotdog} style={styles.hotdog} />
</View>
);
},
},
],
}: RNTesterModule);