diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java index 4a180685d..fbe1cd8c2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java @@ -15,9 +15,10 @@ import android.graphics.Point; import android.view.Display; import android.view.Surface; import android.view.WindowManager; -import com.facebook.react.uimanager.ReactShadowNodeImpl; -import com.facebook.yoga.YogaUnit; + +import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.yoga.YogaValue; +import com.facebook.yoga.YogaUnit; /** * FlatReactModalShadowNode @@ -42,11 +43,11 @@ class FlatReactModalShadowNode extends FlatShadowNode implements AndroidView { /** * We need to set the styleWidth and styleHeight of the one child (represented by the - * within the in Modal.js. This needs to fill the entire window. + * within the in Modal.js. This needs to fill the entire window. */ @Override @TargetApi(16) - public void addChildAt(ReactShadowNodeImpl child, int i) { + public void addChildAt(ReactShadowNode child, int i) { super.addChildAt(child, i); Context context = getThemedContext(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java index f9959b8b8..450dafd19 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java @@ -9,17 +9,18 @@ package com.facebook.react.flat; +import javax.annotation.Nullable; + import android.graphics.Rect; + import com.facebook.infer.annotation.Assertions; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.OnLayoutEvent; -import com.facebook.react.uimanager.ReactClippingViewGroupHelper; import com.facebook.react.uimanager.ReactShadowNode; -import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; -import javax.annotation.Nullable; +import com.facebook.react.uimanager.ReactClippingViewGroupHelper; /** * FlatShadowNode is a base class for all shadow node used in FlatUIImplementation. It extends @@ -212,7 +213,7 @@ import javax.annotation.Nullable; } @Override - public void addChildAt(ReactShadowNodeImpl child, int i) { + public void addChildAt(ReactShadowNode child, int i) { super.addChildAt(child, i); if (mForceMountChildrenToView && child instanceof FlatShadowNode) { ((FlatShadowNode) child).forceMountToView(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/MoveProxy.java b/ReactAndroid/src/main/java/com/facebook/react/flat/MoveProxy.java index 060d05173..c384ac952 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/MoveProxy.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/MoveProxy.java @@ -9,11 +9,11 @@ package com.facebook.react.flat; +import javax.annotation.Nullable; + import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.ReactShadowNode; -import com.facebook.react.uimanager.ReactShadowNodeImpl; -import javax.annotation.Nullable; /** * Helper class that sorts moveFrom/moveTo arrays in lockstep. @@ -23,7 +23,7 @@ import javax.annotation.Nullable; private @Nullable ReadableArray mMoveTo; private int mSize; private int[] mMapping = new int[8]; - private ReactShadowNode[] mChildren = new ReactShadowNodeImpl[4]; + private ReactShadowNode[] mChildren = new ReactShadowNode[4]; /** * Retuns size of underlying moveTo/moveFrom arrays diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java index 06f1b35b4..5ef0cb8e0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java @@ -10,7 +10,6 @@ package com.facebook.react.flat; import com.facebook.react.uimanager.ReactShadowNode; -import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIViewOperationQueue; @@ -19,6 +18,7 @@ import com.facebook.react.uimanager.ViewManager; import com.facebook.yoga.YogaMeasureFunction; import com.facebook.yoga.YogaUnit; import com.facebook.yoga.YogaValue; + import javax.annotation.Nullable; /* package */ final class NativeViewWrapper extends FlatShadowNode implements AndroidView { @@ -95,7 +95,7 @@ import javax.annotation.Nullable; } @Override - public void addChildAt(ReactShadowNodeImpl child, int i) { + public void addChildAt(ReactShadowNode child, int i) { super.addChildAt(child, i); if (mForceMountGrandChildrenToView && child instanceof FlatShadowNode) { ((FlatShadowNode) child).forceMountChildrenToView(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java index f50749fb1..735542c1d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java @@ -9,16 +9,18 @@ package com.facebook.react.flat; +import javax.annotation.Nullable; + import android.graphics.Typeface; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; + import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.ReactShadowNodeImpl; +import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; -import javax.annotation.Nullable; /** * RCTVirtualText is a {@link FlatTextShadowNode} that can contain font styling information. @@ -38,7 +40,7 @@ import javax.annotation.Nullable; private ShadowStyleSpan mShadowStyleSpan = ShadowStyleSpan.INSTANCE; @Override - public void addChildAt(ReactShadowNodeImpl child, int i) { + public void addChildAt(ReactShadowNode child, int i) { super.addChildAt(child, i); notifyChanged(true); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java index 2f0e982fb..7febcdbf4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java @@ -2,36 +2,6 @@ package com.facebook.react.processing; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.PUBLIC; -import static javax.tools.Diagnostic.Kind.ERROR; -import static javax.tools.Diagnostic.Kind.WARNING; - -import com.facebook.infer.annotation.SuppressFieldNotInitialized; -import com.facebook.react.bridge.Dynamic; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.annotations.ReactPropGroup; -import com.facebook.react.uimanager.annotations.ReactPropertyHolder; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.squareup.javapoet.TypeVariableName; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import javax.annotation.Nullable; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; @@ -50,6 +20,37 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.facebook.infer.annotation.SuppressFieldNotInitialized; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.Dynamic; +import com.facebook.react.uimanager.annotations.ReactPropertyHolder; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.annotations.ReactPropGroup; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; + +import static javax.lang.model.element.Modifier.*; +import static javax.tools.Diagnostic.Kind.ERROR; +import static javax.tools.Diagnostic.Kind.WARNING; + /** * This annotation processor crawls subclasses of ReactShadowNode and ViewManager and finds their * exported properties with the @ReactProp or @ReactGroupProp annotation. It generates a class @@ -229,12 +230,13 @@ public class ReactPropertyProcessor extends AbstractProcessor { TypeName typeName = TypeName.get(mirror); if (typeName instanceof ParameterizedTypeName) { ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName; - if (parameterizedTypeName.rawType.equals(VIEW_MANAGER_TYPE) - || parameterizedTypeName.rawType.equals(SHADOW_NODE_TYPE)) { + if (parameterizedTypeName.rawType.equals(VIEW_MANAGER_TYPE)) { return parameterizedTypeName.typeArguments.get(0); } + } else if (typeName.equals(SHADOW_NODE_TYPE)) { + return SHADOW_NODE_TYPE; } else if (typeName.equals(TypeName.OBJECT)) { - throw new IllegalArgumentException("Could not find target type " + typeName); + throw new IllegalArgumentException("Could not find target type"); } List types = mTypes.directSupertypes(mirror); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java index 873c7d09f..3d765969e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java @@ -2,11 +2,13 @@ package com.facebook.react.uimanager; +import javax.annotation.Nullable; + + import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableType; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.annotations.ReactPropGroup; + import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaDisplay; @@ -16,18 +18,19 @@ import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; import com.facebook.yoga.YogaUnit; import com.facebook.yoga.YogaWrap; -import javax.annotation.Nullable; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.annotations.ReactPropGroup; /** - * Supply setters for base view layout properties such as width, height, flex properties, borders, - * etc. + * Supply setters for base view layout properties such as width, height, flex properties, + * borders, etc. * - *

Checking for isVirtual everywhere is a hack to get around the fact that some virtual nodes - * still have layout properties set on them in JS: for example, a component that returns a - * may or may not be embedded in a parent text. There are better solutions that should probably be + * Checking for isVirtual everywhere is a hack to get around the fact that some virtual nodes still + * have layout properties set on them in JS: for example, a component that returns a may + * or may not be embedded in a parent text. There are better solutions that should probably be * explored, namely using the VirtualText class in JS and setting the correct set of validAttributes */ -public class LayoutShadowNode extends ReactShadowNodeImpl { +public class LayoutShadowNode extends ReactShadowNode { /** * A Mutable version of com.facebook.yoga.YogaValue diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 8c6b18be9..9a33f8a4c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -9,180 +9,476 @@ package com.facebook.react.uimanager; -import com.facebook.react.uimanager.annotations.ReactPropertyHolder; +import javax.annotation.Nullable; + +import java.util.Arrays; +import java.util.ArrayList; + import com.facebook.yoga.YogaAlign; -import com.facebook.yoga.YogaBaselineFunction; -import com.facebook.yoga.YogaDirection; +import com.facebook.yoga.YogaConfig; import com.facebook.yoga.YogaDisplay; +import com.facebook.yoga.YogaEdge; +import com.facebook.yoga.YogaConstants; +import com.facebook.yoga.YogaDirection; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaJustify; +import com.facebook.yoga.YogaBaselineFunction; import com.facebook.yoga.YogaMeasureFunction; import com.facebook.yoga.YogaNode; import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; import com.facebook.yoga.YogaValue; import com.facebook.yoga.YogaWrap; -import javax.annotation.Nullable; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.uimanager.annotations.ReactPropertyHolder; /** - * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily for - * layouting therefore it extends {@link YogaNode} to allow that. They also help with handling + * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily + * for layouting therefore it extends {@link YogaNode} to allow that. They also help with handling * Common base subclass of {@link YogaNode} for all layout nodes for react-based view. It extends * {@link YogaNode} by adding additional capabilities. * - *

Instances of this class receive property updates from JS via @{link UIManagerModule}. - * Subclasses may use {@link #updateShadowNode} to persist some of the updated fields in the node - * instance that corresponds to a particular view type. + * Instances of this class receive property updates from JS via @{link UIManagerModule}. Subclasses + * may use {@link #updateShadowNode} to persist some of the updated fields in the node instance that + * corresponds to a particular view type. * - *

Subclasses of {@link ReactShadowNode} should be created only from {@link ViewManager} that + * Subclasses of {@link ReactShadowNode} should be created only from {@link ViewManager} that * corresponds to a certain type of native view. They will be updated and accessed only from JS * thread. Subclasses of {@link ViewManager} may choose to use base class {@link ReactShadowNode} or * custom subclass of it if necessary. * - *

The primary use-case for {@link ReactShadowNode} nodes is to calculate layouting. Although - * this might be extended. For some examples please refer to ARTGroupYogaNode or ReactTextYogaNode. + * The primary use-case for {@link ReactShadowNode} nodes is to calculate layouting. Although this + * might be extended. For some examples please refer to ARTGroupYogaNode or ReactTextYogaNode. * - *

This class allows for the native view hierarchy to not be an exact copy of the hierarchy - * received from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and - * separately native children (e.g. {@link #getNativeChildCount()}). See {@link - * NativeViewHierarchyOptimizer} for more information. + * This class allows for the native view hierarchy to not be an exact copy of the hierarchy received + * from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and separately native + * children (e.g. {@link #getNativeChildCount()}). See {@link NativeViewHierarchyOptimizer} for more + * information. */ @ReactPropertyHolder -public interface ReactShadowNode { +public class ReactShadowNode { + + private int mReactTag; + private @Nullable String mViewClassName; + private @Nullable ReactShadowNode mRootNode; + private @Nullable ThemedReactContext mThemedContext; + private boolean mShouldNotifyOnLayout; + private boolean mNodeUpdated = true; + private @Nullable ArrayList mChildren; + private @Nullable ReactShadowNode mParent; + + // layout-only nodes + private boolean mIsLayoutOnly; + private int mTotalNativeChildren = 0; + private @Nullable ReactShadowNode mNativeParent; + private @Nullable ArrayList mNativeChildren; + private int mScreenX; + private int mScreenY; + private int mScreenWidth; + private int mScreenHeight; + private final Spacing mDefaultPadding = new Spacing(0); + private final float[] mPadding = new float[Spacing.ALL + 1]; + private final boolean[] mPaddingIsPercent = new boolean[Spacing.ALL + 1]; + private final YogaNode mYogaNode; + private static YogaConfig sYogaConfig; + + public ReactShadowNode() { + if (!isVirtual()) { + YogaNode node = YogaNodePool.get().acquire(); + if (sYogaConfig == null) { + sYogaConfig = new YogaConfig(); + sYogaConfig.setPointScaleFactor(0f); + sYogaConfig.setUseLegacyStretchBehaviour(true); + } + if (node == null) { + node = new YogaNode(sYogaConfig); + } + mYogaNode = node; + Arrays.fill(mPadding, YogaConstants.UNDEFINED); + } else { + mYogaNode = null; + } + } /** * Nodes that return {@code true} will be treated as "virtual" nodes. That is, nodes that are not * mapped into native views (e.g. nested text node). By default this method returns {@code false}. */ - boolean isVirtual(); + public boolean isVirtual() { + return false; + } /** * Nodes that return {@code true} will be treated as a root view for the virtual nodes tree. It * means that {@link NativeViewHierarchyManager} will not try to perform {@code manageChildren} - * operation on such views. Good example is {@code InputText} view that may have children {@code - * Text} nodes but this whole hierarchy will be mapped to a single android {@link EditText} view. + * operation on such views. Good example is {@code InputText} view that may have children + * {@code Text} nodes but this whole hierarchy will be mapped to a single android {@link EditText} + * view. */ - boolean isVirtualAnchor(); + public boolean isVirtualAnchor() { + return false; + } /** - * Nodes that return {@code true} will not manage (and and remove) child Yoga nodes. For example - * {@link ReactTextInputShadowNode} or {@link ReactTextShadowNode} have child nodes, which do not - * want Yoga to lay out, so in the eyes of Yoga it is a leaf node. Override this method in - * subclass to enforce this requirement. + * Nodes that return {@code true} will not manage (and and remove) child Yoga nodes. + * For example {@link ReactTextInputShadowNode} or {@link ReactTextShadowNode} have child nodes, + * which do not want Yoga to lay out, so in the eyes of Yoga it is a leaf node. + * Override this method in subclass to enforce this requirement. */ - boolean isYogaLeafNode(); + public boolean isYogaLeafNode() { + return isMeasureDefined(); + } - String getViewClass(); + public final String getViewClass() { + return Assertions.assertNotNull(mViewClassName); + } - boolean hasUpdates(); + public final boolean hasUpdates() { + return mNodeUpdated || hasNewLayout() || isDirty(); + } - void markUpdateSeen(); + public final void markUpdateSeen() { + mNodeUpdated = false; + if (hasNewLayout()) { + markLayoutSeen(); + } + } - void markUpdated(); + public void markUpdated() { + if (mNodeUpdated) { + return; + } + mNodeUpdated = true; + ReactShadowNode parent = getParent(); + if (parent != null) { + parent.markUpdated(); + } + } - boolean hasUnseenUpdates(); + public final boolean hasUnseenUpdates() { + return mNodeUpdated; + } - void dirty(); + public void dirty() { + if (!isVirtual()) { + mYogaNode.dirty(); + } + } - boolean isDirty(); + public final boolean isDirty() { + return mYogaNode != null && mYogaNode.isDirty(); + } - void addChildAt(T child, int i); + public void addChildAt(ReactShadowNode child, int i) { + if (child.mParent != null) { + throw new IllegalViewOperationException( + "Tried to add child that already has a parent! Remove it from its parent first."); + } + if (mChildren == null) { + mChildren = new ArrayList(4); + } + mChildren.add(i, child); + child.mParent = this; - T removeChildAt(int i); + // If a CSS node has measure defined, the layout algorithm will not visit its children. Even + // more, it asserts that you don't add children to nodes with measure functions. + if (mYogaNode != null && !isYogaLeafNode()) { + YogaNode childYogaNode = child.mYogaNode; + if (childYogaNode == null) { + throw new RuntimeException( + "Cannot add a child that doesn't have a YogaNode to a parent without a measure " + + "function! (Trying to add a '" + child.getClass().getSimpleName() + "' to a '" + + getClass().getSimpleName() + "')"); + } + mYogaNode.addChildAt(childYogaNode, i); + } + markUpdated(); - int getChildCount(); + int increase = child.mIsLayoutOnly ? child.mTotalNativeChildren : 1; + mTotalNativeChildren += increase; - T getChildAt(int i); + updateNativeChildrenCountInParent(increase); + } - int indexOf(T child); + public ReactShadowNode removeChildAt(int i) { + if (mChildren == null) { + throw new ArrayIndexOutOfBoundsException( + "Index " + i + " out of bounds: node has no children"); + } + ReactShadowNode removed = mChildren.remove(i); + removed.mParent = null; - void removeAndDisposeAllChildren(); + if (mYogaNode != null && !isYogaLeafNode()) { + mYogaNode.removeChildAt(i); + } + markUpdated(); + + int decrease = removed.mIsLayoutOnly ? removed.mTotalNativeChildren : 1; + mTotalNativeChildren -= decrease; + updateNativeChildrenCountInParent(-decrease); + return removed; + } + + public final int getChildCount() { + return mChildren == null ? 0 : mChildren.size(); + } + + public final ReactShadowNode getChildAt(int i) { + if (mChildren == null) { + throw new ArrayIndexOutOfBoundsException( + "Index " + i + " out of bounds: node has no children"); + } + return mChildren.get(i); + } + + public final int indexOf(ReactShadowNode child) { + return mChildren == null ? -1 : mChildren.indexOf(child); + } + + public void removeAndDisposeAllChildren() { + if (getChildCount() == 0) { + return; + } + + int decrease = 0; + for (int i = getChildCount() - 1; i >= 0; i--) { + if (mYogaNode != null && !isYogaLeafNode()) { + mYogaNode.removeChildAt(i); + } + ReactShadowNode toRemove = getChildAt(i); + toRemove.mParent = null; + toRemove.dispose(); + + decrease += toRemove.mIsLayoutOnly ? toRemove.mTotalNativeChildren : 1; + } + Assertions.assertNotNull(mChildren).clear(); + markUpdated(); + + mTotalNativeChildren -= decrease; + updateNativeChildrenCountInParent(-decrease); + } + + private void updateNativeChildrenCountInParent(int delta) { + if (mIsLayoutOnly) { + ReactShadowNode parent = getParent(); + while (parent != null) { + parent.mTotalNativeChildren += delta; + if (!parent.mIsLayoutOnly) { + break; + } + parent = parent.getParent(); + } + } + } /** * This method will be called by {@link UIManagerModule} once per batch, before calculating - * layout. Will be only called for nodes that are marked as updated with {@link #markUpdated()} or - * require layouting (marked with {@link #dirty()}). + * layout. Will be only called for nodes that are marked as updated with {@link #markUpdated()} + * or require layouting (marked with {@link #dirty()}). */ - void onBeforeLayout(); + public void onBeforeLayout() { + } - void updateProperties(ReactStylesDiffMap props); + public final void updateProperties(ReactStylesDiffMap props) { + ViewManagerPropertyUpdater.updateProps(this, props); + onAfterUpdateTransaction(); + } - void onAfterUpdateTransaction(); + public void onAfterUpdateTransaction() { + // no-op + } /** * Called after layout step at the end of the UI batch from {@link UIManagerModule}. May be used - * to enqueue additional ui operations for the native view. Will only be called on nodes marked as - * updated either with {@link #dirty()} or {@link #markUpdated()}. + * to enqueue additional ui operations for the native view. Will only be called on nodes marked + * as updated either with {@link #dirty()} or {@link #markUpdated()}. * * @param uiViewOperationQueue interface for enqueueing UI operations */ - void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue); + public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { + } - /** @return true if layout (position or dimensions) changed, false otherwise. */ + /** + * @return true if layout (position or dimensions) changed, false otherwise. + */ /* package */ boolean dispatchUpdates( float absoluteX, float absoluteY, UIViewOperationQueue uiViewOperationQueue, - NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); + NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { + if (mNodeUpdated) { + onCollectExtraUpdates(uiViewOperationQueue); + } - int getReactTag(); + if (hasNewLayout()) { + float layoutX = getLayoutX(); + float layoutY = getLayoutY(); + int newAbsoluteLeft = Math.round(absoluteX + layoutX); + int newAbsoluteTop = Math.round(absoluteY + layoutY); + int newAbsoluteRight = Math.round(absoluteX + layoutX + getLayoutWidth()); + int newAbsoluteBottom = Math.round(absoluteY + layoutY + getLayoutHeight()); - void setReactTag(int reactTag); + int newScreenX = Math.round(layoutX); + int newScreenY = Math.round(layoutY); + int newScreenWidth = newAbsoluteRight - newAbsoluteLeft; + int newScreenHeight = newAbsoluteBottom - newAbsoluteTop; - T getRootNode(); + boolean layoutHasChanged = + newScreenX != mScreenX || + newScreenY != mScreenY || + newScreenWidth != mScreenWidth || + newScreenHeight != mScreenHeight; - void setRootNode(T rootNode); + mScreenX = newScreenX; + mScreenY = newScreenY; + mScreenWidth = newScreenWidth; + mScreenHeight = newScreenHeight; - void setViewClassName(String viewClassName); + if (layoutHasChanged) { + nativeViewHierarchyOptimizer.handleUpdateLayout(this); + } - @Nullable - T getParent(); + return layoutHasChanged; + } else { + return false; + } + } + + public final int getReactTag() { + return mReactTag; + } + + public void setReactTag(int reactTag) { + mReactTag = reactTag; + } + + public final ReactShadowNode getRootNode() { + return Assertions.assertNotNull(mRootNode); + } + + /* package */ final void setRootNode(ReactShadowNode rootNode) { + mRootNode = rootNode; + } + + /* package */ final void setViewClassName(String viewClassName) { + mViewClassName = viewClassName; + } + + public final @Nullable ReactShadowNode getParent() { + return mParent; + } /** * Get the {@link ThemedReactContext} associated with this {@link ReactShadowNode}. This will * never change during the lifetime of a {@link ReactShadowNode} instance, but different instances * can have different contexts; don't cache any calculations based on theme values globally. */ - ThemedReactContext getThemedContext(); + public final ThemedReactContext getThemedContext() { + return Assertions.assertNotNull(mThemedContext); + } - void setThemedContext(ThemedReactContext themedContext); + public void setThemedContext(ThemedReactContext themedContext) { + mThemedContext = themedContext; + } - boolean shouldNotifyOnLayout(); + public final boolean shouldNotifyOnLayout() { + return mShouldNotifyOnLayout; + } - void calculateLayout(); + public void calculateLayout() { + mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + } - boolean hasNewLayout(); + public final boolean hasNewLayout() { + return mYogaNode != null && mYogaNode.hasNewLayout(); + } - void markLayoutSeen(); + public final void markLayoutSeen() { + if (mYogaNode != null) { + mYogaNode.markLayoutSeen(); + } + } /** * Adds a child that the native view hierarchy will have at this index in the native view * corresponding to this node. */ - void addNativeChildAt(T child, int nativeIndex); + public final void addNativeChildAt(ReactShadowNode child, int nativeIndex) { + Assertions.assertCondition(!mIsLayoutOnly); + Assertions.assertCondition(!child.mIsLayoutOnly); - T removeNativeChildAt(int i); + if (mNativeChildren == null) { + mNativeChildren = new ArrayList<>(4); + } - void removeAllNativeChildren(); + mNativeChildren.add(nativeIndex, child); + child.mNativeParent = this; + } - int getNativeChildCount(); + public final ReactShadowNode removeNativeChildAt(int i) { + Assertions.assertNotNull(mNativeChildren); + ReactShadowNode removed = mNativeChildren.remove(i); + removed.mNativeParent = null; + return removed; + } - int indexOfNativeChild(T nativeChild); + public final void removeAllNativeChildren() { + if (mNativeChildren != null) { + for (int i = mNativeChildren.size() - 1; i >= 0; i--) { + mNativeChildren.get(i).mNativeParent = null; + } + mNativeChildren.clear(); + } + } - @Nullable - T getNativeParent(); + public final int getNativeChildCount() { + return mNativeChildren == null ? 0 : mNativeChildren.size(); + } + + public final int indexOfNativeChild(ReactShadowNode nativeChild) { + Assertions.assertNotNull(mNativeChildren); + return mNativeChildren.indexOf(nativeChild); + } + + public final @Nullable ReactShadowNode getNativeParent() { + return mNativeParent; + } /** - * Sets whether this node only contributes to the layout of its children without doing any drawing - * or functionality itself. + * Sets whether this node only contributes to the layout of its children without doing any + * drawing or functionality itself. */ - void setIsLayoutOnly(boolean isLayoutOnly); + public final void setIsLayoutOnly(boolean isLayoutOnly) { + Assertions.assertCondition(getParent() == null, "Must remove from no opt parent first"); + Assertions.assertCondition(mNativeParent == null, "Must remove from native parent first"); + Assertions.assertCondition(getNativeChildCount() == 0, "Must remove all native children first"); + mIsLayoutOnly = isLayoutOnly; + } - boolean isLayoutOnly(); + public final boolean isLayoutOnly() { + return mIsLayoutOnly; + } - int getTotalNativeChildren(); + public final int getTotalNativeChildren() { + return mTotalNativeChildren; + } - boolean isDescendantOf(T ancestorNode); + public boolean isDescendantOf(ReactShadowNode ancestorNode) { + ReactShadowNode parentNode = getParent(); + + boolean isDescendant = false; + + while (parentNode != null) { + if (parentNode == ancestorNode) { + isDescendant = true; + break; + } else { + parentNode = parentNode.getParent(); + } + } + + return isDescendant; + } /** * Returns the offset within the native children owned by all layout-only nodes in the subtree @@ -192,143 +488,368 @@ public interface ReactShadowNode { * in this subtree (which means that the given child will be a sibling of theirs in the final * native hierarchy since they'll get attached to the same native parent). * - *

Basically, a view might have children that have been optimized away by {@link - * NativeViewHierarchyOptimizer}. Since those children will then add their native children to this - * view, we now have ranges of native children that correspond to single unoptimized children. The - * purpose of this method is to return the index within the native children that corresponds to - * the **start** of the native children that belong to the given child. Also, note that all of the - * children of a view might be optimized away, so this could return the same value for multiple - * different children. + * Basically, a view might have children that have been optimized away by + * {@link NativeViewHierarchyOptimizer}. Since those children will then add their native children + * to this view, we now have ranges of native children that correspond to single unoptimized + * children. The purpose of this method is to return the index within the native children that + * corresponds to the **start** of the native children that belong to the given child. Also, note + * that all of the children of a view might be optimized away, so this could return the same value + * for multiple different children. * - *

Example. Native children are represented by (N) where N is the no-opt child they came from. - * If no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) + * Example. Native children are represented by (N) where N is the no-opt child they came from. If + * no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) * - *

In case some children are optimized away, it might look like this: (0) (1) (1) (1) (3) (3) - * (4) + * In case some children are optimized away, it might look like this: + * (0) (1) (1) (1) (3) (3) (4) * - *

In that case: getNativeOffsetForChild(Node 0) => 0 getNativeOffsetForChild(Node 1) => 1 - * getNativeOffsetForChild(Node 2) => 4 getNativeOffsetForChild(Node 3) => 4 + * In that case: + * getNativeOffsetForChild(Node 0) => 0 + * getNativeOffsetForChild(Node 1) => 1 + * getNativeOffsetForChild(Node 2) => 4 + * getNativeOffsetForChild(Node 3) => 4 * getNativeOffsetForChild(Node 4) => 6 */ - int getNativeOffsetForChild(T child); + public final int getNativeOffsetForChild(ReactShadowNode child) { + int index = 0; + boolean found = false; + for (int i = 0; i < getChildCount(); i++) { + ReactShadowNode current = getChildAt(i); + if (child == current) { + found = true; + break; + } + index += (current.mIsLayoutOnly ? current.getTotalNativeChildren() : 1); + } + if (!found) { + throw new RuntimeException("Child " + child.mReactTag + " was not a child of " + mReactTag); + } + return index; + } - float getLayoutX(); + public final float getLayoutX() { + return mYogaNode.getLayoutX(); + } - float getLayoutY(); + public final float getLayoutY() { + return mYogaNode.getLayoutY(); + } - float getLayoutWidth(); + public final float getLayoutWidth() { + return mYogaNode.getLayoutWidth(); + } - float getLayoutHeight(); + public final float getLayoutHeight() { + return mYogaNode.getLayoutHeight(); + } - /** @return the x position of the corresponding view on the screen, rounded to pixels */ - int getScreenX(); + /** + * @return the x position of the corresponding view on the screen, rounded to pixels + */ + public int getScreenX() { + return mScreenX; + } - /** @return the y position of the corresponding view on the screen, rounded to pixels */ - int getScreenY(); + /** + * @return the y position of the corresponding view on the screen, rounded to pixels + */ + public int getScreenY() { + return mScreenY; + } - /** @return width corrected for rounding to pixels. */ - int getScreenWidth(); + /** + * @return width corrected for rounding to pixels. + */ + public int getScreenWidth() { + return mScreenWidth; + } - /** @return height corrected for rounding to pixels. */ - int getScreenHeight(); + /** + * @return height corrected for rounding to pixels. + */ + public int getScreenHeight() { + return mScreenHeight; + } - YogaDirection getLayoutDirection(); + public final YogaDirection getLayoutDirection() { + return mYogaNode.getLayoutDirection(); + } - void setLayoutDirection(YogaDirection direction); + public void setLayoutDirection(YogaDirection direction) { + mYogaNode.setDirection(direction); + } - YogaValue getStyleWidth(); + public final YogaValue getStyleWidth() { + return mYogaNode.getWidth(); + } - void setStyleWidth(float widthPx); + public void setStyleWidth(float widthPx) { + mYogaNode.setWidth(widthPx); + } - void setStyleWidthPercent(float percent); + public void setStyleWidthPercent(float percent) { + mYogaNode.setWidthPercent(percent); + } - void setStyleWidthAuto(); + public void setStyleWidthAuto() { + mYogaNode.setWidthAuto(); + } - void setStyleMinWidth(float widthPx); + public void setStyleMinWidth(float widthPx) { + mYogaNode.setMinWidth(widthPx); + } - void setStyleMinWidthPercent(float percent); + public void setStyleMinWidthPercent(float percent) { + mYogaNode.setMinWidthPercent(percent); + } - void setStyleMaxWidth(float widthPx); + public void setStyleMaxWidth(float widthPx) { + mYogaNode.setMaxWidth(widthPx); + } - void setStyleMaxWidthPercent(float percent); + public void setStyleMaxWidthPercent(float percent) { + mYogaNode.setMaxWidthPercent(percent); + } - YogaValue getStyleHeight(); + public final YogaValue getStyleHeight() { + return mYogaNode.getHeight(); + } - void setStyleHeight(float heightPx); + public void setStyleHeight(float heightPx) { + mYogaNode.setHeight(heightPx); + } - void setStyleHeightPercent(float percent); + public void setStyleHeightPercent(float percent) { + mYogaNode.setHeightPercent(percent); + } - void setStyleHeightAuto(); + public void setStyleHeightAuto() { + mYogaNode.setHeightAuto(); + } - void setStyleMinHeight(float widthPx); + public void setStyleMinHeight(float widthPx) { + mYogaNode.setMinHeight(widthPx); + } - void setStyleMinHeightPercent(float percent); + public void setStyleMinHeightPercent(float percent) { + mYogaNode.setMinHeightPercent(percent); + } - void setStyleMaxHeight(float widthPx); + public void setStyleMaxHeight(float widthPx) { + mYogaNode.setMaxHeight(widthPx); + } - void setStyleMaxHeightPercent(float percent); + public void setStyleMaxHeightPercent(float percent) { + mYogaNode.setMaxHeightPercent(percent); + } - void setFlex(float flex); + public void setFlex(float flex) { + mYogaNode.setFlex(flex); + } - void setFlexGrow(float flexGrow); + public void setFlexGrow(float flexGrow) { + mYogaNode.setFlexGrow(flexGrow); + } - void setFlexShrink(float flexShrink); + public void setFlexShrink(float flexShrink) { + mYogaNode.setFlexShrink(flexShrink); + } - void setFlexBasis(float flexBasis); + public void setFlexBasis(float flexBasis) { + mYogaNode.setFlexBasis(flexBasis); + } - void setFlexBasisAuto(); + public void setFlexBasisAuto() { + mYogaNode.setFlexBasisAuto(); + } - void setFlexBasisPercent(float percent); + public void setFlexBasisPercent(float percent) { + mYogaNode.setFlexBasisPercent(percent); + } - void setStyleAspectRatio(float aspectRatio); + public void setStyleAspectRatio(float aspectRatio) { + mYogaNode.setAspectRatio(aspectRatio); + } - void setFlexDirection(YogaFlexDirection flexDirection); + public void setFlexDirection(YogaFlexDirection flexDirection) { + mYogaNode.setFlexDirection(flexDirection); + } - void setFlexWrap(YogaWrap wrap); + public void setFlexWrap(YogaWrap wrap) { + mYogaNode.setWrap(wrap); + } - void setAlignSelf(YogaAlign alignSelf); + public void setAlignSelf(YogaAlign alignSelf) { + mYogaNode.setAlignSelf(alignSelf); + } - void setAlignItems(YogaAlign alignItems); + public void setAlignItems(YogaAlign alignItems) { + mYogaNode.setAlignItems(alignItems); + } - void setAlignContent(YogaAlign alignContent); + public void setAlignContent(YogaAlign alignContent) { + mYogaNode.setAlignContent(alignContent); + } - void setJustifyContent(YogaJustify justifyContent); + public void setJustifyContent(YogaJustify justifyContent) { + mYogaNode.setJustifyContent(justifyContent); + } - void setOverflow(YogaOverflow overflow); + public void setOverflow(YogaOverflow overflow) { + mYogaNode.setOverflow(overflow); + } - void setDisplay(YogaDisplay display); + public void setDisplay(YogaDisplay display) { + mYogaNode.setDisplay(display); + } - void setMargin(int spacingType, float margin); + public void setMargin(int spacingType, float margin) { + mYogaNode.setMargin(YogaEdge.fromInt(spacingType), margin); + } - void setMarginPercent(int spacingType, float percent); + public void setMarginPercent(int spacingType, float percent) { + mYogaNode.setMarginPercent(YogaEdge.fromInt(spacingType), percent); + } - void setMarginAuto(int spacingType); + public void setMarginAuto(int spacingType) { + mYogaNode.setMarginAuto(YogaEdge.fromInt(spacingType)); + } - float getPadding(int spacingType); + public final float getPadding(int spacingType) { + return mYogaNode.getLayoutPadding(YogaEdge.fromInt(spacingType)); + } - YogaValue getStylePadding(int spacingType); + public final YogaValue getStylePadding(int spacingType) { + return mYogaNode.getPadding(YogaEdge.fromInt(spacingType)); + } - void setDefaultPadding(int spacingType, float padding); + public void setDefaultPadding(int spacingType, float padding) { + mDefaultPadding.set(spacingType, padding); + updatePadding(); + } - void setPadding(int spacingType, float padding); + public void setPadding(int spacingType, float padding) { + mPadding[spacingType] = padding; + mPaddingIsPercent[spacingType] = false; + updatePadding(); + } - void setPaddingPercent(int spacingType, float percent); + public void setPaddingPercent(int spacingType, float percent) { + mPadding[spacingType] = percent; + mPaddingIsPercent[spacingType] = !YogaConstants.isUndefined(percent); + updatePadding(); + } - void setBorder(int spacingType, float borderWidth); + private void updatePadding() { + for (int spacingType = Spacing.LEFT; spacingType <= Spacing.ALL; spacingType++) { + if (spacingType == Spacing.LEFT || + spacingType == Spacing.RIGHT || + spacingType == Spacing.START || + spacingType == Spacing.END) { + if (YogaConstants.isUndefined(mPadding[spacingType]) && + YogaConstants.isUndefined(mPadding[Spacing.HORIZONTAL]) && + YogaConstants.isUndefined(mPadding[Spacing.ALL])) { + mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); + continue; + } + } else if (spacingType == Spacing.TOP || spacingType == Spacing.BOTTOM) { + if (YogaConstants.isUndefined(mPadding[spacingType]) && + YogaConstants.isUndefined(mPadding[Spacing.VERTICAL]) && + YogaConstants.isUndefined(mPadding[Spacing.ALL])) { + mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); + continue; + } + } else { + if (YogaConstants.isUndefined(mPadding[spacingType])) { + mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); + continue; + } + } - void setPosition(int spacingType, float position); + if (mPaddingIsPercent[spacingType]) { + mYogaNode.setPaddingPercent(YogaEdge.fromInt(spacingType), mPadding[spacingType]); + } else { + mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding[spacingType]); + } + } + } - void setPositionPercent(int spacingType, float percent); + public void setBorder(int spacingType, float borderWidth) { + mYogaNode.setBorder(YogaEdge.fromInt(spacingType), borderWidth); + } - void setPositionType(YogaPositionType positionType); + public void setPosition(int spacingType, float position) { + mYogaNode.setPosition(YogaEdge.fromInt(spacingType), position); + } - void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout); + public void setPositionPercent(int spacingType, float percent) { + mYogaNode.setPositionPercent(YogaEdge.fromInt(spacingType), percent); + } - void setBaselineFunction(YogaBaselineFunction baselineFunction); + public void setPositionType(YogaPositionType positionType) { + mYogaNode.setPositionType(positionType); + } - void setMeasureFunction(YogaMeasureFunction measureFunction); + public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { + mShouldNotifyOnLayout = shouldNotifyOnLayout; + } - boolean isMeasureDefined(); + public void setBaselineFunction(YogaBaselineFunction baselineFunction) { + mYogaNode.setBaselineFunction(baselineFunction); + } - void dispose(); + public void setMeasureFunction(YogaMeasureFunction measureFunction) { + if ((measureFunction == null ^ mYogaNode.isMeasureDefined()) && + getChildCount() != 0) { + throw new RuntimeException( + "Since a node with a measure function does not add any native yoga children, it's " + + "not safe to transition to/from having a measure function unless a node has no children"); + } + mYogaNode.setMeasureFunction(measureFunction); + } + + public boolean isMeasureDefined() { + return mYogaNode.isMeasureDefined(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + toStringWithIndentation(sb, 0); + return sb.toString(); + } + + private void toStringWithIndentation(StringBuilder result, int level) { + // Spaces and tabs are dropped by IntelliJ logcat integration, so rely on __ instead. + for (int i = 0; i < level; ++i) { + result.append("__"); + } + + result + .append(getClass().getSimpleName()) + .append(" "); + if (mYogaNode != null) { + result + .append(getLayoutWidth()) + .append(",") + .append(getLayoutHeight()); + } else { + result.append("(virtual node)"); + } + result.append("\n"); + + if (getChildCount() == 0) { + return; + } + + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).toStringWithIndentation(result, level + 1); + } + } + + public void dispose() { + if (mYogaNode != null) { + mYogaNode.reset(); + YogaNodePool.get().release(mYogaNode); + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java deleted file mode 100644 index 1ec64ec60..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ /dev/null @@ -1,940 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.uimanager; - -import com.facebook.infer.annotation.Assertions; -import com.facebook.yoga.YogaAlign; -import com.facebook.yoga.YogaBaselineFunction; -import com.facebook.yoga.YogaConfig; -import com.facebook.yoga.YogaConstants; -import com.facebook.yoga.YogaDirection; -import com.facebook.yoga.YogaDisplay; -import com.facebook.yoga.YogaEdge; -import com.facebook.yoga.YogaFlexDirection; -import com.facebook.yoga.YogaJustify; -import com.facebook.yoga.YogaMeasureFunction; -import com.facebook.yoga.YogaNode; -import com.facebook.yoga.YogaOverflow; -import com.facebook.yoga.YogaPositionType; -import com.facebook.yoga.YogaValue; -import com.facebook.yoga.YogaWrap; -import java.util.ArrayList; -import java.util.Arrays; -import javax.annotation.Nullable; - -/** - * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily for - * layouting therefore it extends {@link YogaNode} to allow that. They also help with handling - * Common base subclass of {@link YogaNode} for all layout nodes for react-based view. It extends - * {@link YogaNode} by adding additional capabilities. - * - *

Instances of this class receive property updates from JS via @{link UIManagerModule}. - * Subclasses may use {@link #updateShadowNode} to persist some of the updated fields in the node - * instance that corresponds to a particular view type. - * - *

Subclasses of {@link ReactShadowNodeImpl} should be created only from {@link ViewManager} that - * corresponds to a certain type of native view. They will be updated and accessed only from JS - * thread. Subclasses of {@link ViewManager} may choose to use base class {@link - * ReactShadowNodeImpl} or custom subclass of it if necessary. - * - *

The primary use-case for {@link ReactShadowNodeImpl} nodes is to calculate layouting. Although - * this might be extended. For some examples please refer to ARTGroupYogaNode or ReactTextYogaNode. - * - *

This class allows for the native view hierarchy to not be an exact copy of the hierarchy - * received from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and - * separately native children (e.g. {@link #getNativeChildCount()}). See {@link - * NativeViewHierarchyOptimizer} for more information. - */ -public class ReactShadowNodeImpl implements ReactShadowNode { - - private int mReactTag; - private @Nullable String mViewClassName; - private @Nullable ReactShadowNodeImpl mRootNode; - private @Nullable ThemedReactContext mThemedContext; - private boolean mShouldNotifyOnLayout; - private boolean mNodeUpdated = true; - private @Nullable ArrayList mChildren; - private @Nullable ReactShadowNodeImpl mParent; - - // layout-only nodes - private boolean mIsLayoutOnly; - private int mTotalNativeChildren = 0; - private @Nullable ReactShadowNodeImpl mNativeParent; - private @Nullable ArrayList mNativeChildren; - private int mScreenX; - private int mScreenY; - private int mScreenWidth; - private int mScreenHeight; - private final Spacing mDefaultPadding = new Spacing(0); - private final float[] mPadding = new float[Spacing.ALL + 1]; - private final boolean[] mPaddingIsPercent = new boolean[Spacing.ALL + 1]; - private final YogaNode mYogaNode; - private static YogaConfig sYogaConfig; - - public ReactShadowNodeImpl() { - if (!isVirtual()) { - YogaNode node = YogaNodePool.get().acquire(); - if (sYogaConfig == null) { - sYogaConfig = new YogaConfig(); - sYogaConfig.setPointScaleFactor(0f); - sYogaConfig.setUseLegacyStretchBehaviour(true); - } - if (node == null) { - node = new YogaNode(sYogaConfig); - } - mYogaNode = node; - Arrays.fill(mPadding, YogaConstants.UNDEFINED); - } else { - mYogaNode = null; - } - } - - /** - * Nodes that return {@code true} will be treated as "virtual" nodes. That is, nodes that are not - * mapped into native views (e.g. nested text node). By default this method returns {@code false}. - */ - @Override - public boolean isVirtual() { - return false; - } - - /** - * Nodes that return {@code true} will be treated as a root view for the virtual nodes tree. It - * means that {@link NativeViewHierarchyManager} will not try to perform {@code manageChildren} - * operation on such views. Good example is {@code InputText} view that may have children {@code - * Text} nodes but this whole hierarchy will be mapped to a single android {@link EditText} view. - */ - @Override - public boolean isVirtualAnchor() { - return false; - } - - /** - * Nodes that return {@code true} will not manage (and and remove) child Yoga nodes. For example - * {@link ReactTextInputShadowNode} or {@link ReactTextShadowNode} have child nodes, which do not - * want Yoga to lay out, so in the eyes of Yoga it is a leaf node. Override this method in - * subclass to enforce this requirement. - */ - @Override - public boolean isYogaLeafNode() { - return isMeasureDefined(); - } - - @Override - public final String getViewClass() { - return Assertions.assertNotNull(mViewClassName); - } - - @Override - public final boolean hasUpdates() { - return mNodeUpdated || hasNewLayout() || isDirty(); - } - - @Override - public final void markUpdateSeen() { - mNodeUpdated = false; - if (hasNewLayout()) { - markLayoutSeen(); - } - } - - @Override - public void markUpdated() { - if (mNodeUpdated) { - return; - } - mNodeUpdated = true; - ReactShadowNodeImpl parent = getParent(); - if (parent != null) { - parent.markUpdated(); - } - } - - @Override - public final boolean hasUnseenUpdates() { - return mNodeUpdated; - } - - @Override - public void dirty() { - if (!isVirtual()) { - mYogaNode.dirty(); - } - } - - @Override - public final boolean isDirty() { - return mYogaNode != null && mYogaNode.isDirty(); - } - - @Override - public void addChildAt(ReactShadowNodeImpl child, int i) { - if (child.getParent() != null) { - throw new IllegalViewOperationException( - "Tried to add child that already has a parent! Remove it from its parent first."); - } - if (mChildren == null) { - mChildren = new ArrayList(4); - } - mChildren.add(i, child); - child.mParent = this; - - // If a CSS node has measure defined, the layout algorithm will not visit its children. Even - // more, it asserts that you don't add children to nodes with measure functions. - if (mYogaNode != null && !isYogaLeafNode()) { - YogaNode childYogaNode = child.mYogaNode; - if (childYogaNode == null) { - throw new RuntimeException( - "Cannot add a child that doesn't have a YogaNode to a parent without a measure " + - "function! (Trying to add a '" + child.getClass().getSimpleName() + "' to a '" + - getClass().getSimpleName() + "')"); - } - mYogaNode.addChildAt(childYogaNode, i); - } - markUpdated(); - - int increase = child.isLayoutOnly() ? child.getTotalNativeChildren() : 1; - mTotalNativeChildren += increase; - - updateNativeChildrenCountInParent(increase); - } - - @Override - public ReactShadowNodeImpl removeChildAt(int i) { - if (mChildren == null) { - throw new ArrayIndexOutOfBoundsException( - "Index " + i + " out of bounds: node has no children"); - } - ReactShadowNodeImpl removed = mChildren.remove(i); - removed.mParent = null; - - if (mYogaNode != null && !isYogaLeafNode()) { - mYogaNode.removeChildAt(i); - } - markUpdated(); - - int decrease = removed.isLayoutOnly() ? removed.getTotalNativeChildren() : 1; - mTotalNativeChildren -= decrease; - updateNativeChildrenCountInParent(-decrease); - return removed; - } - - @Override - public final int getChildCount() { - return mChildren == null ? 0 : mChildren.size(); - } - - @Override - public final ReactShadowNodeImpl getChildAt(int i) { - if (mChildren == null) { - throw new ArrayIndexOutOfBoundsException( - "Index " + i + " out of bounds: node has no children"); - } - return mChildren.get(i); - } - - @Override - public final int indexOf(ReactShadowNodeImpl child) { - return mChildren == null ? -1 : mChildren.indexOf(child); - } - - @Override - public void removeAndDisposeAllChildren() { - if (getChildCount() == 0) { - return; - } - - int decrease = 0; - for (int i = getChildCount() - 1; i >= 0; i--) { - if (mYogaNode != null && !isYogaLeafNode()) { - mYogaNode.removeChildAt(i); - } - ReactShadowNodeImpl toRemove = getChildAt(i); - toRemove.mParent = null; - toRemove.dispose(); - - decrease += toRemove.isLayoutOnly() ? toRemove.getTotalNativeChildren() : 1; - } - Assertions.assertNotNull(mChildren).clear(); - markUpdated(); - - mTotalNativeChildren -= decrease; - updateNativeChildrenCountInParent(-decrease); - } - - private void updateNativeChildrenCountInParent(int delta) { - if (mIsLayoutOnly) { - ReactShadowNodeImpl parent = getParent(); - while (parent != null) { - parent.mTotalNativeChildren += delta; - if (!parent.isLayoutOnly()) { - break; - } - parent = parent.getParent(); - } - } - } - - /** - * This method will be called by {@link UIManagerModule} once per batch, before calculating - * layout. Will be only called for nodes that are marked as updated with {@link #markUpdated()} or - * require layouting (marked with {@link #dirty()}). - */ - @Override - public void onBeforeLayout() {} - - @Override - public final void updateProperties(ReactStylesDiffMap props) { - ViewManagerPropertyUpdater.updateProps(this, props); - onAfterUpdateTransaction(); - } - - @Override - public void onAfterUpdateTransaction() { - // no-op - } - - /** - * Called after layout step at the end of the UI batch from {@link UIManagerModule}. May be used - * to enqueue additional ui operations for the native view. Will only be called on nodes marked as - * updated either with {@link #dirty()} or {@link #markUpdated()}. - * - * @param uiViewOperationQueue interface for enqueueing UI operations - */ - @Override - public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {} - - /** @return true if layout (position or dimensions) changed, false otherwise. */ - @Override - public boolean dispatchUpdates( - float absoluteX, - float absoluteY, - UIViewOperationQueue uiViewOperationQueue, - NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { - if (mNodeUpdated) { - onCollectExtraUpdates(uiViewOperationQueue); - } - - if (hasNewLayout()) { - float layoutX = getLayoutX(); - float layoutY = getLayoutY(); - int newAbsoluteLeft = Math.round(absoluteX + layoutX); - int newAbsoluteTop = Math.round(absoluteY + layoutY); - int newAbsoluteRight = Math.round(absoluteX + layoutX + getLayoutWidth()); - int newAbsoluteBottom = Math.round(absoluteY + layoutY + getLayoutHeight()); - - int newScreenX = Math.round(layoutX); - int newScreenY = Math.round(layoutY); - int newScreenWidth = newAbsoluteRight - newAbsoluteLeft; - int newScreenHeight = newAbsoluteBottom - newAbsoluteTop; - - boolean layoutHasChanged = - newScreenX != mScreenX || - newScreenY != mScreenY || - newScreenWidth != mScreenWidth || - newScreenHeight != mScreenHeight; - - mScreenX = newScreenX; - mScreenY = newScreenY; - mScreenWidth = newScreenWidth; - mScreenHeight = newScreenHeight; - - if (layoutHasChanged) { - nativeViewHierarchyOptimizer.handleUpdateLayout(this); - } - - return layoutHasChanged; - } else { - return false; - } - } - - @Override - public final int getReactTag() { - return mReactTag; - } - - @Override - public void setReactTag(int reactTag) { - mReactTag = reactTag; - } - - @Override - public final ReactShadowNodeImpl getRootNode() { - return Assertions.assertNotNull(mRootNode); - } - - @Override - public final void setRootNode(ReactShadowNodeImpl rootNode) { - mRootNode = rootNode; - } - - @Override - public final void setViewClassName(String viewClassName) { - mViewClassName = viewClassName; - } - - @Override - public final @Nullable ReactShadowNodeImpl getParent() { - return mParent; - } - - /** - * Get the {@link ThemedReactContext} associated with this {@link ReactShadowNodeImpl}. This will - * never change during the lifetime of a {@link ReactShadowNodeImpl} instance, but different - * instances can have different contexts; don't cache any calculations based on theme values - * globally. - */ - @Override - public final ThemedReactContext getThemedContext() { - return Assertions.assertNotNull(mThemedContext); - } - - @Override - public void setThemedContext(ThemedReactContext themedContext) { - mThemedContext = themedContext; - } - - @Override - public final boolean shouldNotifyOnLayout() { - return mShouldNotifyOnLayout; - } - - @Override - public void calculateLayout() { - mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); - } - - @Override - public final boolean hasNewLayout() { - return mYogaNode != null && mYogaNode.hasNewLayout(); - } - - @Override - public final void markLayoutSeen() { - if (mYogaNode != null) { - mYogaNode.markLayoutSeen(); - } - } - - /** - * Adds a child that the native view hierarchy will have at this index in the native view - * corresponding to this node. - */ - @Override - public final void addNativeChildAt(ReactShadowNodeImpl child, int nativeIndex) { - Assertions.assertCondition(!mIsLayoutOnly); - Assertions.assertCondition(!child.mIsLayoutOnly); - - if (mNativeChildren == null) { - mNativeChildren = new ArrayList<>(4); - } - - mNativeChildren.add(nativeIndex, child); - child.mNativeParent = this; - } - - @Override - public final ReactShadowNodeImpl removeNativeChildAt(int i) { - Assertions.assertNotNull(mNativeChildren); - ReactShadowNodeImpl removed = mNativeChildren.remove(i); - removed.mNativeParent = null; - return removed; - } - - @Override - public final void removeAllNativeChildren() { - if (mNativeChildren != null) { - for (int i = mNativeChildren.size() - 1; i >= 0; i--) { - mNativeChildren.get(i).mNativeParent = null; - } - mNativeChildren.clear(); - } - } - - @Override - public final int getNativeChildCount() { - return mNativeChildren == null ? 0 : mNativeChildren.size(); - } - - @Override - public final int indexOfNativeChild(ReactShadowNodeImpl nativeChild) { - Assertions.assertNotNull(mNativeChildren); - return mNativeChildren.indexOf(nativeChild); - } - - @Override - public final @Nullable ReactShadowNodeImpl getNativeParent() { - return mNativeParent; - } - - /** - * Sets whether this node only contributes to the layout of its children without doing any drawing - * or functionality itself. - */ - @Override - public final void setIsLayoutOnly(boolean isLayoutOnly) { - Assertions.assertCondition(getParent() == null, "Must remove from no opt parent first"); - Assertions.assertCondition(mNativeParent == null, "Must remove from native parent first"); - Assertions.assertCondition(getNativeChildCount() == 0, "Must remove all native children first"); - mIsLayoutOnly = isLayoutOnly; - } - - @Override - public final boolean isLayoutOnly() { - return mIsLayoutOnly; - } - - @Override - public final int getTotalNativeChildren() { - return mTotalNativeChildren; - } - - - @Override - public boolean isDescendantOf(ReactShadowNodeImpl ancestorNode) { - ReactShadowNodeImpl parentNode = getParent(); - - boolean isDescendant = false; - - while (parentNode != null) { - if (parentNode == ancestorNode) { - isDescendant = true; - break; - } else { - parentNode = parentNode.getParent(); - } - } - - return isDescendant; - } - - /** - * Returns the offset within the native children owned by all layout-only nodes in the subtree - * rooted at this node for the given child. Put another way, this returns the number of native - * nodes (nodes not optimized out of the native tree) that are a) to the left (visited before by a - * DFS) of the given child in the subtree rooted at this node and b) do not have a native parent - * in this subtree (which means that the given child will be a sibling of theirs in the final - * native hierarchy since they'll get attached to the same native parent). - * - *

Basically, a view might have children that have been optimized away by {@link - * NativeViewHierarchyOptimizer}. Since those children will then add their native children to this - * view, we now have ranges of native children that correspond to single unoptimized children. The - * purpose of this method is to return the index within the native children that corresponds to - * the **start** of the native children that belong to the given child. Also, note that all of the - * children of a view might be optimized away, so this could return the same value for multiple - * different children. - * - *

Example. Native children are represented by (N) where N is the no-opt child they came from. - * If no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) - * - *

In case some children are optimized away, it might look like this: (0) (1) (1) (1) (3) (3) - * (4) - * - *

In that case: getNativeOffsetForChild(Node 0) => 0 getNativeOffsetForChild(Node 1) => 1 - * getNativeOffsetForChild(Node 2) => 4 getNativeOffsetForChild(Node 3) => 4 - * getNativeOffsetForChild(Node 4) => 6 - */ - @Override - public final int getNativeOffsetForChild(ReactShadowNodeImpl child) { - int index = 0; - boolean found = false; - for (int i = 0; i < getChildCount(); i++) { - ReactShadowNodeImpl current = getChildAt(i); - if (child == current) { - found = true; - break; - } - index += (current.isLayoutOnly() ? current.getTotalNativeChildren() : 1); - } - if (!found) { - throw new RuntimeException( - "Child " + child.getReactTag() + " was not a child of " + mReactTag); - } - return index; - } - - @Override - public final float getLayoutX() { - return mYogaNode.getLayoutX(); - } - - @Override - public final float getLayoutY() { - return mYogaNode.getLayoutY(); - } - - @Override - public final float getLayoutWidth() { - return mYogaNode.getLayoutWidth(); - } - - @Override - public final float getLayoutHeight() { - return mYogaNode.getLayoutHeight(); - } - - /** @return the x position of the corresponding view on the screen, rounded to pixels */ - @Override - public int getScreenX() { - return mScreenX; - } - - /** @return the y position of the corresponding view on the screen, rounded to pixels */ - @Override - public int getScreenY() { - return mScreenY; - } - - /** @return width corrected for rounding to pixels. */ - @Override - public int getScreenWidth() { - return mScreenWidth; - } - - /** @return height corrected for rounding to pixels. */ - @Override - public int getScreenHeight() { - return mScreenHeight; - } - - @Override - public final YogaDirection getLayoutDirection() { - return mYogaNode.getLayoutDirection(); - } - - @Override - public void setLayoutDirection(YogaDirection direction) { - mYogaNode.setDirection(direction); - } - - @Override - public final YogaValue getStyleWidth() { - return mYogaNode.getWidth(); - } - - @Override - public void setStyleWidth(float widthPx) { - mYogaNode.setWidth(widthPx); - } - - @Override - public void setStyleWidthPercent(float percent) { - mYogaNode.setWidthPercent(percent); - } - - @Override - public void setStyleWidthAuto() { - mYogaNode.setWidthAuto(); - } - - @Override - public void setStyleMinWidth(float widthPx) { - mYogaNode.setMinWidth(widthPx); - } - - @Override - public void setStyleMinWidthPercent(float percent) { - mYogaNode.setMinWidthPercent(percent); - } - - @Override - public void setStyleMaxWidth(float widthPx) { - mYogaNode.setMaxWidth(widthPx); - } - - @Override - public void setStyleMaxWidthPercent(float percent) { - mYogaNode.setMaxWidthPercent(percent); - } - - @Override - public final YogaValue getStyleHeight() { - return mYogaNode.getHeight(); - } - - @Override - public void setStyleHeight(float heightPx) { - mYogaNode.setHeight(heightPx); - } - - @Override - public void setStyleHeightPercent(float percent) { - mYogaNode.setHeightPercent(percent); - } - - @Override - public void setStyleHeightAuto() { - mYogaNode.setHeightAuto(); - } - - @Override - public void setStyleMinHeight(float widthPx) { - mYogaNode.setMinHeight(widthPx); - } - - @Override - public void setStyleMinHeightPercent(float percent) { - mYogaNode.setMinHeightPercent(percent); - } - - @Override - public void setStyleMaxHeight(float widthPx) { - mYogaNode.setMaxHeight(widthPx); - } - - @Override - public void setStyleMaxHeightPercent(float percent) { - mYogaNode.setMaxHeightPercent(percent); - } - - @Override - public void setFlex(float flex) { - mYogaNode.setFlex(flex); - } - - @Override - public void setFlexGrow(float flexGrow) { - mYogaNode.setFlexGrow(flexGrow); - } - - @Override - public void setFlexShrink(float flexShrink) { - mYogaNode.setFlexShrink(flexShrink); - } - - @Override - public void setFlexBasis(float flexBasis) { - mYogaNode.setFlexBasis(flexBasis); - } - - @Override - public void setFlexBasisAuto() { - mYogaNode.setFlexBasisAuto(); - } - - @Override - public void setFlexBasisPercent(float percent) { - mYogaNode.setFlexBasisPercent(percent); - } - - @Override - public void setStyleAspectRatio(float aspectRatio) { - mYogaNode.setAspectRatio(aspectRatio); - } - - @Override - public void setFlexDirection(YogaFlexDirection flexDirection) { - mYogaNode.setFlexDirection(flexDirection); - } - - @Override - public void setFlexWrap(YogaWrap wrap) { - mYogaNode.setWrap(wrap); - } - - @Override - public void setAlignSelf(YogaAlign alignSelf) { - mYogaNode.setAlignSelf(alignSelf); - } - - @Override - public void setAlignItems(YogaAlign alignItems) { - mYogaNode.setAlignItems(alignItems); - } - - @Override - public void setAlignContent(YogaAlign alignContent) { - mYogaNode.setAlignContent(alignContent); - } - - @Override - public void setJustifyContent(YogaJustify justifyContent) { - mYogaNode.setJustifyContent(justifyContent); - } - - @Override - public void setOverflow(YogaOverflow overflow) { - mYogaNode.setOverflow(overflow); - } - - @Override - public void setDisplay(YogaDisplay display) { - mYogaNode.setDisplay(display); - } - - @Override - public void setMargin(int spacingType, float margin) { - mYogaNode.setMargin(YogaEdge.fromInt(spacingType), margin); - } - - @Override - public void setMarginPercent(int spacingType, float percent) { - mYogaNode.setMarginPercent(YogaEdge.fromInt(spacingType), percent); - } - - @Override - public void setMarginAuto(int spacingType) { - mYogaNode.setMarginAuto(YogaEdge.fromInt(spacingType)); - } - - @Override - public final float getPadding(int spacingType) { - return mYogaNode.getLayoutPadding(YogaEdge.fromInt(spacingType)); - } - - @Override - public final YogaValue getStylePadding(int spacingType) { - return mYogaNode.getPadding(YogaEdge.fromInt(spacingType)); - } - - @Override - public void setDefaultPadding(int spacingType, float padding) { - mDefaultPadding.set(spacingType, padding); - updatePadding(); - } - - @Override - public void setPadding(int spacingType, float padding) { - mPadding[spacingType] = padding; - mPaddingIsPercent[spacingType] = false; - updatePadding(); - } - - @Override - public void setPaddingPercent(int spacingType, float percent) { - mPadding[spacingType] = percent; - mPaddingIsPercent[spacingType] = !YogaConstants.isUndefined(percent); - updatePadding(); - } - - private void updatePadding() { - for (int spacingType = Spacing.LEFT; spacingType <= Spacing.ALL; spacingType++) { - if (spacingType == Spacing.LEFT || - spacingType == Spacing.RIGHT || - spacingType == Spacing.START || - spacingType == Spacing.END) { - if (YogaConstants.isUndefined(mPadding[spacingType]) && - YogaConstants.isUndefined(mPadding[Spacing.HORIZONTAL]) && - YogaConstants.isUndefined(mPadding[Spacing.ALL])) { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); - continue; - } - } else if (spacingType == Spacing.TOP || spacingType == Spacing.BOTTOM) { - if (YogaConstants.isUndefined(mPadding[spacingType]) && - YogaConstants.isUndefined(mPadding[Spacing.VERTICAL]) && - YogaConstants.isUndefined(mPadding[Spacing.ALL])) { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); - continue; - } - } else { - if (YogaConstants.isUndefined(mPadding[spacingType])) { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); - continue; - } - } - - if (mPaddingIsPercent[spacingType]) { - mYogaNode.setPaddingPercent(YogaEdge.fromInt(spacingType), mPadding[spacingType]); - } else { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding[spacingType]); - } - } - } - - @Override - public void setBorder(int spacingType, float borderWidth) { - mYogaNode.setBorder(YogaEdge.fromInt(spacingType), borderWidth); - } - - @Override - public void setPosition(int spacingType, float position) { - mYogaNode.setPosition(YogaEdge.fromInt(spacingType), position); - } - - @Override - public void setPositionPercent(int spacingType, float percent) { - mYogaNode.setPositionPercent(YogaEdge.fromInt(spacingType), percent); - } - - @Override - public void setPositionType(YogaPositionType positionType) { - mYogaNode.setPositionType(positionType); - } - - @Override - public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { - mShouldNotifyOnLayout = shouldNotifyOnLayout; - } - - @Override - public void setBaselineFunction(YogaBaselineFunction baselineFunction) { - mYogaNode.setBaselineFunction(baselineFunction); - } - - @Override - public void setMeasureFunction(YogaMeasureFunction measureFunction) { - if ((measureFunction == null ^ mYogaNode.isMeasureDefined()) && - getChildCount() != 0) { - throw new RuntimeException( - "Since a node with a measure function does not add any native yoga children, it's " + - "not safe to transition to/from having a measure function unless a node has no children"); - } - mYogaNode.setMeasureFunction(measureFunction); - } - - @Override - public boolean isMeasureDefined() { - return mYogaNode.isMeasureDefined(); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - toStringWithIndentation(sb, 0); - return sb.toString(); - } - - private void toStringWithIndentation(StringBuilder result, int level) { - // Spaces and tabs are dropped by IntelliJ logcat integration, so rely on __ instead. - for (int i = 0; i < level; ++i) { - result.append("__"); - } - - result - .append(getClass().getSimpleName()) - .append(" "); - if (mYogaNode != null) { - result - .append(getLayoutWidth()) - .append(",") - .append(getLayoutHeight()); - } else { - result.append("(virtual node)"); - } - result.append("\n"); - - if (getChildCount() == 0) { - return; - } - - for (int i = 0; i < getChildCount(); i++) { - getChildAt(i).toStringWithIndentation(result, level + 1); - } - } - - @Override - public void dispose() { - if (mYogaNode != null) { - mYogaNode.reset(); - YogaNodePool.get().release(mYogaNode); - } - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 2d0cc1fee..0a90afd38 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -97,7 +97,7 @@ public class UIImplementation { } protected ReactShadowNode createRootShadowNode() { - ReactShadowNode rootCSSNode = new ReactShadowNodeImpl(); + ReactShadowNode rootCSSNode = new ReactShadowNode(); I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); if (sharedI18nUtilInstance.isRTL(mReactContext)) { rootCSSNode.setLayoutDirection(YogaDirection.RTL); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index 4ef80977a..cfdd34be2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -2,7 +2,15 @@ package com.facebook.react.uimanager; +import javax.annotation.Nullable; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + import android.view.View; + import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; @@ -10,11 +18,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import javax.annotation.Nullable; /** * This class is responsible for holding view manager property setters and is used in a process of @@ -330,7 +333,7 @@ import javax.annotation.Nullable; */ /*package*/ static Map getNativePropSettersForShadowNodeClass( Class cls) { - if (cls == null) { + if (cls == ReactShadowNode.class) { return EMPTY_PROPS_MAP; } Map props = CLASS_PROPS_CACHE.get(cls); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java index ac9739b02..70dc6faaf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java @@ -9,21 +9,23 @@ package com.facebook.react.views.art; +import javax.annotation.Nullable; + import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; + import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.DisplayMetricsHolder; -import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.annotations.ReactProp; -import javax.annotation.Nullable; +import com.facebook.react.uimanager.ReactShadowNode; /** * Base class for ARTView virtual nodes: {@link ARTGroupShadowNode}, {@link ARTShapeShadowNode} and * indirectly for {@link ARTTextShadowNode}. */ -public abstract class ARTVirtualNode extends ReactShadowNodeImpl { +public abstract class ARTVirtualNode extends ReactShadowNode { protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostShadowNode.java index 8c3b5fde6..52f3c2251 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostShadowNode.java @@ -10,8 +10,9 @@ package com.facebook.react.views.modal; import android.graphics.Point; + import com.facebook.react.uimanager.LayoutShadowNode; -import com.facebook.react.uimanager.ReactShadowNodeImpl; +import com.facebook.react.uimanager.ReactShadowNode; /** * We implement the Modal by using an Android Dialog. That will fill the entire window of the @@ -25,10 +26,10 @@ class ModalHostShadowNode extends LayoutShadowNode { /** * We need to set the styleWidth and styleHeight of the one child (represented by the - * within the in Modal.js. This needs to fill the entire window. + * within the in Modal.js. This needs to fill the entire window. */ @Override - public void addChildAt(ReactShadowNodeImpl child, int i) { + public void addChildAt(ReactShadowNode child, int i) { super.addChildAt(child, i); Point modalSize = ModalHostHelper.getModalHostSize(getThemedContext()); child.setStyleWidth(modalSize.x); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java index 6cf0200c7..b97a5bf54 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextShadowNode.java @@ -9,15 +9,16 @@ package com.facebook.react.views.text; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.uimanager.ReactShadowNode; -import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.annotations.ReactProp; import javax.annotation.Nullable; /** - * {@link ReactShadowNode} class for pure raw text node (aka {@code textContent} in terms of DOM). - * Raw text node can only have simple string value without any attributes, properties or state. + * {@link ReactShadowNode} class for pure raw text node + * (aka {@code textContent} in terms of DOM). + * Raw text node can only have simple string value without any attributes, + * properties or state. */ -public class ReactRawTextShadowNode extends ReactShadowNodeImpl { +public class ReactRawTextShadowNode extends ReactShadowNode { @VisibleForTesting public static final String PROP_TEXT = "text"; diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java index 3022fb19f..169a001db 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java @@ -9,25 +9,27 @@ package com.facebook.react.uimanager; +import javax.annotation.Nullable; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.JavaOnlyMap; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.annotations.ReactPropGroup; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Rule; +import org.junit.Test; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RobolectricTestRunner; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.annotations.ReactPropGroup; -import javax.annotation.Nullable; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - /** * Test {@link ReactProp} annotation for {@link ReactShadowNode}. More comprahensive test of this * annotation can be found in {@link ReactPropAnnotationSetterTest} where we test all possible types @@ -59,7 +61,7 @@ public class ReactPropForShadowNodeSetterTest { return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues)); } - private class ShadowViewUnderTest extends ReactShadowNodeImpl { + private class ShadowViewUnderTest extends ReactShadowNode { private ViewManagerUpdatesReceiver mViewManagerUpdatesReceiver; diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java index 8b9717dd7..1ddcdf047 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java @@ -9,12 +9,15 @@ package com.facebook.react.uimanager; +import java.util.Map; + import android.view.View; + import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; -import java.util.Map; -import org.junit.Rule; + import org.junit.Test; +import org.junit.Rule; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.modules.junit4.rule.PowerMockRule; @@ -66,61 +69,55 @@ public class ReactPropForShadowNodeSpecTest { @Test(expected = RuntimeException.class) public void testMethodWithWrongNumberOfParams() { - new BaseViewManager( - new ReactShadowNodeImpl() { - @ReactProp(name = "prop") - public void setterWithIncorrectNumberOfArgs(boolean value, int anotherValue) {} - }.getClass()) - .getNativeProps(); + new BaseViewManager(new ReactShadowNode() { + @ReactProp(name = "prop") + public void setterWithIncorrectNumberOfArgs(boolean value, int anotherValue) { + } + }.getClass()).getNativeProps(); } @Test(expected = RuntimeException.class) public void testMethodWithTooFewParams() { - new BaseViewManager( - new ReactShadowNodeImpl() { - @ReactProp(name = "prop") - public void setterWithNoArgs() {} - }.getClass()) - .getNativeProps(); + new BaseViewManager(new ReactShadowNode() { + @ReactProp(name = "prop") + public void setterWithNoArgs() { + } + }.getClass()).getNativeProps(); } @Test(expected = RuntimeException.class) public void testUnsupportedValueType() { - new BaseViewManager( - new ReactShadowNodeImpl() { - @ReactProp(name = "prop") - public void setterWithMap(Map value) {} - }.getClass()) - .getNativeProps(); + new BaseViewManager(new ReactShadowNode() { + @ReactProp(name = "prop") + public void setterWithMap(Map value) { + } + }.getClass()).getNativeProps(); } @Test(expected = RuntimeException.class) public void testGroupInvalidNumberOfParams() { - new BaseViewManager( - new ReactShadowNodeImpl() { - @ReactPropGroup(names = {"prop1", "prop2"}) - public void setterWithTooManyParams(int index, float value, boolean bool) {} - }.getClass()) - .getNativeProps(); + new BaseViewManager(new ReactShadowNode() { + @ReactPropGroup(names = {"prop1", "prop2"}) + public void setterWithTooManyParams(int index, float value, boolean bool) { + } + }.getClass()).getNativeProps(); } @Test(expected = RuntimeException.class) public void testGroupTooFewParams() { - new BaseViewManager( - new ReactShadowNodeImpl() { - @ReactPropGroup(names = {"prop1", "prop2"}) - public void setterWithTooManyParams(int index) {} - }.getClass()) - .getNativeProps(); + new BaseViewManager(new ReactShadowNode() { + @ReactPropGroup(names = {"prop1", "prop2"}) + public void setterWithTooManyParams(int index) { + } + }.getClass()).getNativeProps(); } @Test(expected = RuntimeException.class) public void testGroupNoIndexParam() { - new BaseViewManager( - new ReactShadowNodeImpl() { - @ReactPropGroup(names = {"prop1", "prop2"}) - public void setterWithTooManyParams(float value, boolean bool) {} - }.getClass()) - .getNativeProps(); + new BaseViewManager(new ReactShadowNode() { + @ReactPropGroup(names = {"prop1", "prop2"}) + public void setterWithTooManyParams(float value, boolean bool) { + } + }.getClass()).getNativeProps(); } }