Skip to content

Commit

Permalink
Remove CSSBackgroundDrawable.getResolvedLayoutDirection(), [RN][Andro…
Browse files Browse the repository at this point in the history
…id] Propagate layout direction to Android views

Differential Revision: D57248417
  • Loading branch information
NickGerleman authored and facebook-github-bot committed May 16, 2024
1 parent 801a607 commit 03d8423
Show file tree
Hide file tree
Showing 27 changed files with 217 additions and 79 deletions.
9 changes: 4 additions & 5 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -2790,7 +2790,7 @@ public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
public fun setJSResponder (IIZ)V
public fun stopSurface ()V
public fun updateEventEmitter (ILcom/facebook/react/fabric/events/EventEmitterWrapper;)V
public fun updateLayout (IIIIIII)V
public fun updateLayout (IIIIIIII)V
public fun updateOverflowInset (IIIII)V
public fun updatePadding (IIIII)V
public fun updateProps (ILcom/facebook/react/bridge/ReadableMap;)V
Expand Down Expand Up @@ -5527,12 +5527,11 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro
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 getResolvedLayoutDirection ()I
public fun hasRoundedBorders ()Z
protected fun onBoundsChange (Landroid/graphics/Rect;)V
public fun onResolvedLayoutDirectionChanged (I)Z
public fun paddingBoxPath ()Landroid/graphics/Path;
public fun setAlpha (I)V
public fun setBorderColor (IFF)V
Expand All @@ -5542,9 +5541,9 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro
public fun setBorderWidth (IF)V
public fun setColor (I)V
public fun setColorFilter (Landroid/graphics/ColorFilter;)V
public fun setLayoutDirectionOverride (I)V
public fun setRadius (F)V
public fun setRadius (FI)V
public fun setResolvedLayoutDirection (I)Z
}

public abstract interface class com/facebook/react/uimanager/events/BatchEventDispatchedListener {
Expand Down Expand Up @@ -6633,6 +6632,7 @@ public class com/facebook/react/views/scroll/OnScrollDispatchHelper {

public class com/facebook/react/views/scroll/ReactHorizontalScrollContainerView : com/facebook/react/views/view/ReactViewGroup {
public fun <init> (Landroid/content/Context;)V
public fun getLayoutDirection ()I
protected fun onLayout (ZIIII)V
public fun setRemoveClippedSubviews (Z)V
}
Expand Down Expand Up @@ -7927,7 +7927,6 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
protected fun onLayout (ZIIII)V
protected fun onMeasure (II)V
public fun onRtlPropertiesChanged (I)V
protected fun onSizeChanged (IIII)V
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
public fun removeView (Landroid/view/View;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.touch.JSResponderHandler;
import com.facebook.react.uimanager.IViewGroupManager;
Expand Down Expand Up @@ -934,7 +935,14 @@ public void sendAccessibilityEvent(int reactTag, int eventType) {

@UiThread
public void updateLayout(
int reactTag, int parentTag, int x, int y, int width, int height, int displayType) {
int reactTag,
int parentTag,
int x,
int y,
int width,
int height,
int displayType,
int layoutDirection) {
if (isStopped()) {
return;
}
Expand All @@ -950,6 +958,13 @@ public void updateLayout(
throw new IllegalStateException("Unable to find View for tag: " + reactTag);
}

if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
viewToUpdate.setLayoutDirection(
layoutDirection == 1
? View.LAYOUT_DIRECTION_LTR
: layoutDirection == 2 ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_INHERIT);
}

viewToUpdate.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.systrace.Systrace;

Expand Down Expand Up @@ -150,9 +151,14 @@ public void execute(MountingManager mountingManager) {
int height = mIntBuffer[i++];
int displayType = mIntBuffer[i++];

surfaceMountingManager.updateLayout(
reactTag, parentTag, x, y, width, height, displayType);

if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
int layoutDirection = mIntBuffer[i++];
surfaceMountingManager.updateLayout(
reactTag, parentTag, x, y, width, height, displayType, layoutDirection);
} else {
surfaceMountingManager.updateLayout(
reactTag, parentTag, x, y, width, height, displayType, 0);
}
} else if (type == INSTRUCTION_UPDATE_PADDING) {
surfaceMountingManager.updatePadding(
mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c35ee97cf5c4b5f77cdd045341f2848b>>
* @generated SignedSource<<0d5f4b26573154fb42c312b03c0dc6a7>>
*/

/**
Expand Down Expand Up @@ -124,6 +124,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun preventDoubleTextMeasure(): Boolean = accessor.preventDoubleTextMeasure()

/**
* Propagate layout direction to Android views.
*/
@JvmStatic
public fun setAndroidLayoutDirection(): Boolean = accessor.setAndroidLayoutDirection()

/**
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<1b84e5fa96120a511db6f831afb73eab>>
* @generated SignedSource<<60f7a791ff3eb270c26ebf598f65d8d5>>
*/

/**
Expand Down Expand Up @@ -36,6 +36,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var preventDoubleTextMeasureCache: Boolean? = null
private var setAndroidLayoutDirectionCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useStateAlignmentMechanismCache: Boolean? = null
Expand Down Expand Up @@ -184,6 +185,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
return cached
}

override fun setAndroidLayoutDirection(): Boolean {
var cached = setAndroidLayoutDirectionCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.setAndroidLayoutDirection()
setAndroidLayoutDirectionCache = cached
}
return cached
}

override fun useModernRuntimeScheduler(): Boolean {
var cached = useModernRuntimeSchedulerCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<2cd7ab4688ca2179ba3a19e9a062f695>>
* @generated SignedSource<<a217de50b6148069ad6ff915c1446f45>>
*/

/**
Expand Down Expand Up @@ -60,6 +60,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun preventDoubleTextMeasure(): Boolean

@DoNotStrip @JvmStatic public external fun setAndroidLayoutDirection(): Boolean

@DoNotStrip @JvmStatic public external fun useModernRuntimeScheduler(): Boolean

@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<a2c4f2c950a7b92163fdc6219864d6ca>>
* @generated SignedSource<<c86391a2ae3dc6fbf5b8327b37bc30d2>>
*/

/**
Expand Down Expand Up @@ -55,6 +55,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun preventDoubleTextMeasure(): Boolean = false

override fun setAndroidLayoutDirection(): Boolean = false

override fun useModernRuntimeScheduler(): Boolean = false

override fun useNativeViewConfigsInBridgelessMode(): Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<48f657b282ef658ab7e9024f470d37ad>>
* @generated SignedSource<<8f6a50230ef99335baa5929070b94ddc>>
*/

/**
Expand Down Expand Up @@ -40,6 +40,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var preventDoubleTextMeasureCache: Boolean? = null
private var setAndroidLayoutDirectionCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useStateAlignmentMechanismCache: Boolean? = null
Expand Down Expand Up @@ -204,6 +205,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun setAndroidLayoutDirection(): Boolean {
var cached = setAndroidLayoutDirectionCache
if (cached == null) {
cached = currentProvider.setAndroidLayoutDirection()
accessedFeatureFlags.add("setAndroidLayoutDirection")
setAndroidLayoutDirectionCache = cached
}
return cached
}

override fun useModernRuntimeScheduler(): Boolean {
var cached = useModernRuntimeSchedulerCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0f3d31f94f4bded41936fe4ecafbdd4a>>
* @generated SignedSource<<fcc8430187565628d83479182550ff21>>
*/

/**
Expand Down Expand Up @@ -55,6 +55,8 @@ public interface ReactNativeFeatureFlagsProvider {

@DoNotStrip public fun preventDoubleTextMeasure(): Boolean

@DoNotStrip public fun setAndroidLayoutDirection(): Boolean

@DoNotStrip public fun useModernRuntimeScheduler(): Boolean

@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react.uimanager.drawable;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
Expand Down Expand Up @@ -119,7 +120,9 @@ private enum BorderStyle {
private BorderRadiusStyle mBorderRadius = new BorderRadiusStyle();
private ComputedBorderRadius mComputedBorderRadius = new ComputedBorderRadius();
private final Context mContext;
private int mLayoutDirection;

// Should be removed after migrating to Android layout direction.
private int mLayoutDirectionOverride = -1;

public CSSBackgroundDrawable(Context context) {
mContext = context;
Expand Down Expand Up @@ -163,6 +166,19 @@ public void setColorFilter(ColorFilter cf) {
// do nothing
}

@Deprecated
public void setLayoutDirectionOverride(int layoutDirection) {
if (mLayoutDirectionOverride != layoutDirection) {
mLayoutDirectionOverride = layoutDirection;
}
}

@Override
@SuppressLint("WrongConstant")
public int getLayoutDirection() {
return mLayoutDirectionOverride == -1 ? super.getLayoutDirection() : mLayoutDirectionOverride;
}

@Override
public int getOpacity() {
return (Color.alpha(mColor) * mAlpha) >> 8;
Expand Down Expand Up @@ -292,25 +308,6 @@ public void setColor(int color) {
invalidateSelf();
}

/** Similar to Drawable.getLayoutDirection, but available in APIs < 23. */
public int getResolvedLayoutDirection() {
return mLayoutDirection;
}

/** Similar to Drawable.setLayoutDirection, but available in APIs < 23. */
public boolean setResolvedLayoutDirection(int layoutDirection) {
if (mLayoutDirection != layoutDirection) {
mLayoutDirection = layoutDirection;
return onResolvedLayoutDirectionChanged(layoutDirection);
}
return false;
}

/** Similar to Drawable.onLayoutDirectionChanged, but available in APIs < 23. */
public boolean onResolvedLayoutDirectionChanged(int layoutDirection) {
return false;
}

@VisibleForTesting
public int getColor() {
return mColor;
Expand Down Expand Up @@ -392,7 +389,7 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) {
// Clip inner border
canvas.clipPath(mInnerClipPathForBorderRadius, Region.Op.DIFFERENCE);

final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
int colorStart = getBorderColor(Spacing.START);
int colorEnd = getBorderColor(Spacing.END);

Expand Down Expand Up @@ -591,7 +588,7 @@ private void updatePath() {

mComputedBorderRadius =
mBorderRadius.resolve(
mLayoutDirection,
getLayoutDirection(),
mContext,
mOuterClipTempRectForBorderRadius.width(),
mOuterClipTempRectForBorderRadius.height());
Expand Down Expand Up @@ -1080,7 +1077,7 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) {
colorTop = colorBlockStart;
}

final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
int colorStart = getBorderColor(Spacing.START);
int colorEnd = getBorderColor(Spacing.END);

Expand Down Expand Up @@ -1305,7 +1302,7 @@ public RectF getDirectionAwareBorderInsets() {
float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);

if (mBorderWidth != null) {
final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
float borderStartWidth = mBorderWidth.getRaw(Spacing.START);
float borderEndWidth = mBorderWidth.getRaw(Spacing.END);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.Context;
import androidx.core.view.ViewCompat;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.views.view.ReactViewGroup;

Expand All @@ -27,14 +28,22 @@ public ReactHorizontalScrollContainerView(Context context) {
: ViewCompat.LAYOUT_DIRECTION_LTR;
}

@Override
public int getLayoutDirection() {
if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
return super.getLayoutDirection();
}
return mLayoutDirection;
}

@Override
public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
// Clipping doesn't work well for horizontal scroll views in RTL mode - in both
// Fabric and non-Fabric - especially with TextInputs. The behavior you could see
// is TextInputs being blurred immediately after being focused. So, for now,
// it's easier to just disable this for these specific RTL views.
// TODO T86027499: support `setRemoveClippedSubviews` in RTL mode
if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
super.setRemoveClippedSubviews(false);
return;
}
Expand All @@ -44,7 +53,7 @@ public void setRemoveClippedSubviews(boolean removeClippedSubviews) {

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
// When the layout direction is RTL, we expect Yoga to give us a layout
// that extends off the screen to the left so we re-center it with left=0
int newLeft = 0;
Expand Down
Loading

0 comments on commit 03d8423

Please sign in to comment.