From 1f6642b34a694b6f82582d0b50f55ec14530943e Mon Sep 17 00:00:00 2001 From: Ahmed El-Helw Date: Thu, 18 Aug 2016 12:50:23 -0700 Subject: [PATCH] Support RTL for Nodes Summary: Nodes supports RTL now, just like non-Nodes does. Differential Revision: D3727028 --- .../react/flat/FlatUIImplementation.java | 11 +++++- .../facebook/react/flat/FlatViewGroup.java | 13 +++---- .../java/com/facebook/react/flat/RCTText.java | 35 +++++++++++++++---- .../com/facebook/react/flat/RCTTextInput.java | 12 ++++--- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java index d3ba4bbd6..68907fb3e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java @@ -15,10 +15,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import com.facebook.csslayout.CSSDirection; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.UIImplementation; @@ -82,6 +84,7 @@ public class FlatUIImplementation extends UIImplementation { private final MoveProxy mMoveProxy = new MoveProxy(); private final StateBuilder mStateBuilder; private @Nullable ReactImageManager mReactImageManager; + private final ReactApplicationContext mReactContext; private FlatUIImplementation( ReactApplicationContext reactContext, @@ -89,6 +92,7 @@ public class FlatUIImplementation extends UIImplementation { ViewManagerRegistry viewManagers, FlatUIViewOperationQueue operationsQueue) { super(reactContext, viewManagers, operationsQueue); + mReactContext = reactContext; mStateBuilder = new StateBuilder(operationsQueue); mReactImageManager = reactImageManager; } @@ -105,7 +109,12 @@ public class FlatUIImplementation extends UIImplementation { mReactImageManager = null; } - return new FlatRootShadowNode(); + ReactShadowNode node = new FlatRootShadowNode(); + I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); + if (sharedI18nUtilInstance.isRTL(mReactContext)) { + node.setDirection(CSSDirection.RTL); + } + return node; } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java index 35e84881c..dc5f8f677 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java @@ -201,14 +201,14 @@ import com.facebook.react.views.view.ReactClippingViewGroup; @Override public int reactTagForTouch(float touchX, float touchY) { - /** + /* * Make sure we don't find any children if the pointer events are set to BOX_ONLY. * There is no need to special-case any other modes, because if PointerEvents are set to: * a) PointerEvents.AUTO - all children are included, nothing to exclude * b) PointerEvents.NONE - this method will NOT be executed, because the View will be filtered * out by TouchTargetHelper. * c) PointerEvents.BOX_NONE - TouchTargetHelper will make sure that {@link #reactTagForTouch()} - * doesn't return getId(). + * doesn't return getId(). */ SoftAssertions.assertCondition( mPointerEvents != PointerEvents.NONE, @@ -1011,11 +1011,12 @@ import com.facebook.react.views.view.ReactClippingViewGroup; right = Math.max(right, child.getRight()); bottom = Math.max(bottom, child.getBottom()); } - for (int i = 0; i < mDrawCommands.length; i++) { - if (!(mDrawCommands[i] instanceof AbstractDrawCommand)) { + + for (DrawCommand mDrawCommand : mDrawCommands) { + if (!(mDrawCommand instanceof AbstractDrawCommand)) { continue; } - AbstractDrawCommand drawCommand = (AbstractDrawCommand) mDrawCommands[i]; + AbstractDrawCommand drawCommand = (AbstractDrawCommand) mDrawCommand; left = Math.min(left, Math.round(drawCommand.getLeft())); top = Math.min(top, Math.round(drawCommand.getTop())); right = Math.max(right, Math.round(drawCommand.getRight())); @@ -1180,7 +1181,7 @@ import com.facebook.react.views.view.ReactClippingViewGroup; return mHitSlopRect; } - public void setHitSlopRect(@Nullable Rect rect) { + /* package */ void setHitSlopRect(@Nullable Rect rect) { mHitSlopRect = rect; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java index 3d7794f4e..d9dd5b1dd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java @@ -14,7 +14,9 @@ import javax.annotation.Nullable; import android.support.v4.text.TextDirectionHeuristicsCompat; import android.text.Layout; import android.text.TextUtils; +import android.view.Gravity; +import com.facebook.csslayout.CSSDirection; import com.facebook.csslayout.CSSMeasureMode; import com.facebook.csslayout.CSSNodeAPI; import com.facebook.csslayout.MeasureOutput; @@ -49,7 +51,7 @@ import com.facebook.textcachewarmer.DefaultTextLayoutCacheWarmer; private float mSpacingMult = 1.0f; private float mSpacingAdd = 0.0f; private int mNumberOfLines = Integer.MAX_VALUE; - private Layout.Alignment mAlignment = Layout.Alignment.ALIGN_NORMAL; + private int mAlignment = Gravity.NO_GRAVITY; public RCTText() { setMeasureFunction(this); @@ -98,7 +100,7 @@ import com.facebook.textcachewarmer.DefaultTextLayoutCacheWarmer; mSpacingAdd, mSpacingMult, getFontStyle(), - mAlignment); + getAlignment()); if (mDrawCommand != null && !mDrawCommand.isFrozen()) { mDrawCommand.setLayout(layout); @@ -163,7 +165,7 @@ import com.facebook.textcachewarmer.DefaultTextLayoutCacheWarmer; mSpacingAdd, mSpacingMult, getFontStyle(), - mAlignment)); + getAlignment())); updateNodeRegion = true; } @@ -259,21 +261,40 @@ import com.facebook.textcachewarmer.DefaultTextLayoutCacheWarmer; @ReactProp(name = ViewProps.TEXT_ALIGN) public void setTextAlign(@Nullable String textAlign) { if (textAlign == null || "auto".equals(textAlign)) { - mAlignment = Layout.Alignment.ALIGN_NORMAL; + mAlignment = Gravity.NO_GRAVITY; } else if ("left".equals(textAlign)) { // left and right may yield potentially different results (relative to non-nodes) in cases // when supportsRTL="true" in the manifest. - mAlignment = Layout.Alignment.ALIGN_NORMAL; + mAlignment = Gravity.LEFT; } else if ("right".equals(textAlign)) { - mAlignment = Layout.Alignment.ALIGN_OPPOSITE; + mAlignment = Gravity.RIGHT; } else if ("center".equals(textAlign)) { - mAlignment = Layout.Alignment.ALIGN_CENTER; + mAlignment = Gravity.CENTER; } else { throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); } + notifyChanged(false); } + public Layout.Alignment getAlignment() { + boolean isRtl = getLayoutDirection() == CSSDirection.RTL; + switch (mAlignment) { + // Layout.Alignment.RIGHT and Layout.Alignment.LEFT are @hide :( + case Gravity.LEFT: + int index = isRtl ? /* RIGHT */ 4 : /* LEFT */ 3; + return Layout.Alignment.values()[index]; + case Gravity.RIGHT: + index = isRtl ? /* LEFT */ 3 : /* RIGHT */ 4; + return Layout.Alignment.values()[index]; + case Gravity.CENTER: + return Layout.Alignment.ALIGN_CENTER; + case Gravity.NO_GRAVITY: + default: + return Layout.Alignment.ALIGN_NORMAL; + } + } + private static Layout createTextLayout( int width, CSSMeasureMode widthMode, diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextInput.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextInput.java index 00b232a7f..ba002a0da 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextInput.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextInput.java @@ -11,11 +11,14 @@ package com.facebook.react.flat; import javax.annotation.Nullable; +import android.annotation.TargetApi; +import android.os.Build; import android.text.SpannableStringBuilder; import android.util.TypedValue; import android.view.ViewGroup; import android.widget.EditText; +import com.facebook.csslayout.CSSDirection; import com.facebook.csslayout.CSSMeasureMode; import com.facebook.csslayout.CSSNodeAPI; import com.facebook.csslayout.MeasureOutput; @@ -53,6 +56,7 @@ public class RCTTextInput extends RCTVirtualText implements AndroidView, CSSNode markUpdated(); } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void setThemedContext(ThemedReactContext themedContext) { super.setThemedContext(themedContext); @@ -65,9 +69,9 @@ public class RCTTextInput extends RCTVirtualText implements AndroidView, CSSNode ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - setDefaultPadding(Spacing.LEFT, mEditText.getPaddingLeft()); + setDefaultPadding(Spacing.START, mEditText.getPaddingStart()); setDefaultPadding(Spacing.TOP, mEditText.getPaddingTop()); - setDefaultPadding(Spacing.RIGHT, mEditText.getPaddingRight()); + setDefaultPadding(Spacing.END, mEditText.getPaddingEnd()); setDefaultPadding(Spacing.BOTTOM, mEditText.getPaddingBottom()); } @@ -89,9 +93,9 @@ public class RCTTextInput extends RCTVirtualText implements AndroidView, CSSNode (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) : fontSize); Spacing padding = getPadding(); editText.setPadding( - (int) Math.ceil(padding.get(Spacing.LEFT)), + (int) Math.ceil(padding.get(Spacing.START)), (int) Math.ceil(padding.get(Spacing.TOP)), - (int) Math.ceil(padding.get(Spacing.RIGHT)), + (int) Math.ceil(padding.get(Spacing.END)), (int) Math.ceil(padding.get(Spacing.BOTTOM))); if (mNumberOfLines != UNSET) {