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 fbe1cd8c26..4a180685d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java @@ -15,10 +15,9 @@ import android.graphics.Point; import android.view.Display; import android.view.Surface; import android.view.WindowManager; - -import com.facebook.react.uimanager.ReactShadowNode; -import com.facebook.yoga.YogaValue; +import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.yoga.YogaUnit; +import com.facebook.yoga.YogaValue; /** * FlatReactModalShadowNode @@ -43,11 +42,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(ReactShadowNode child, int i) { + public void addChildAt(ReactShadowNodeImpl 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 450dafd19c..f9959b8b86 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java @@ -9,18 +9,17 @@ 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 com.facebook.react.uimanager.ReactClippingViewGroupHelper; +import javax.annotation.Nullable; /** * FlatShadowNode is a base class for all shadow node used in FlatUIImplementation. It extends @@ -213,7 +212,7 @@ import com.facebook.react.uimanager.ReactClippingViewGroupHelper; } @Override - public void addChildAt(ReactShadowNode child, int i) { + public void addChildAt(ReactShadowNodeImpl 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 c384ac9526..060d051733 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 com.facebook.react.uimanager.ReactShadowNode; private @Nullable ReadableArray mMoveTo; private int mSize; private int[] mMapping = new int[8]; - private ReactShadowNode[] mChildren = new ReactShadowNode[4]; + private ReactShadowNode[] mChildren = new ReactShadowNodeImpl[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 5ef0cb8e06..06f1b35b43 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java @@ -10,6 +10,7 @@ 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; @@ -18,7 +19,6 @@ 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(ReactShadowNode child, int i) { + public void addChildAt(ReactShadowNodeImpl 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 735542c1df..f50749fb18 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java @@ -9,18 +9,16 @@ 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.ReactShadowNode; +import com.facebook.react.uimanager.ReactShadowNodeImpl; 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. @@ -40,7 +38,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; private ShadowStyleSpan mShadowStyleSpan = ShadowStyleSpan.INSTANCE; @Override - public void addChildAt(ReactShadowNode child, int i) { + public void addChildAt(ReactShadowNodeImpl 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 7febcdbf46..a78daa34bb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java @@ -2,6 +2,36 @@ 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; @@ -20,37 +50,6 @@ 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 @@ -73,8 +72,8 @@ public class ReactPropertyProcessor extends AbstractProcessor { private static final TypeName VIEW_MANAGER_TYPE = ClassName.get("com.facebook.react.uimanager", "ViewManager"); - private static final TypeName SHADOW_NODE_TYPE = - ClassName.get("com.facebook.react.uimanager", "ReactShadowNode"); + private static final TypeName SHADOW_NODE_IMPL_TYPE = + ClassName.get("com.facebook.react.uimanager", "ReactShadowNodeImpl"); private static final ClassName VIEW_MANAGER_SETTER_TYPE = ClassName.get( @@ -185,9 +184,13 @@ public class ReactPropertyProcessor extends AbstractProcessor { return true; } + private boolean isShadowNodeType(TypeName typeName) { + return typeName.equals(SHADOW_NODE_IMPL_TYPE); + } + private ClassInfo parseClass(ClassName className, TypeElement typeElement) { TypeName targetType = getTargetType(typeElement.asType()); - TypeName viewType = targetType.equals(SHADOW_NODE_TYPE) ? null : targetType; + TypeName viewType = isShadowNodeType(targetType) ? null : targetType; ClassInfo classInfo = new ClassInfo(className, typeElement, viewType); findProperties(classInfo, typeElement); @@ -233,10 +236,10 @@ public class ReactPropertyProcessor extends AbstractProcessor { 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 (isShadowNodeType(typeName)) { + return SHADOW_NODE_IMPL_TYPE; } else if (typeName.equals(TypeName.OBJECT)) { - throw new IllegalArgumentException("Could not find target type"); + throw new IllegalArgumentException("Could not find target type " + typeName); } 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 3d765969e4..873c7d09f2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java @@ -2,13 +2,11 @@ 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; @@ -18,19 +16,18 @@ import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; import com.facebook.yoga.YogaUnit; import com.facebook.yoga.YogaWrap; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.annotations.ReactPropGroup; +import javax.annotation.Nullable; /** - * 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 ReactShadowNode { +public class LayoutShadowNode extends ReactShadowNodeImpl { /** * 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 9a33f8a4c8..08526b3871 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -1,484 +1,185 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. + * 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. + *

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 javax.annotation.Nullable; - -import java.util.Arrays; -import java.util.ArrayList; - import com.facebook.yoga.YogaAlign; -import com.facebook.yoga.YogaConfig; -import com.facebook.yoga.YogaDisplay; -import com.facebook.yoga.YogaEdge; -import com.facebook.yoga.YogaConstants; +import com.facebook.yoga.YogaBaselineFunction; import com.facebook.yoga.YogaDirection; +import com.facebook.yoga.YogaDisplay; 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 com.facebook.infer.annotation.Assertions; -import com.facebook.react.uimanager.annotations.ReactPropertyHolder; +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 + * 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 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; - } - } +public interface ReactShadowNode { /** * 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}. */ - public boolean isVirtual() { - return false; - } + boolean isVirtual(); /** * 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. */ - public boolean isVirtualAnchor() { - return false; - } + boolean isVirtualAnchor(); /** - * 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. */ - public boolean isYogaLeafNode() { - return isMeasureDefined(); - } + boolean isYogaLeafNode(); - public final String getViewClass() { - return Assertions.assertNotNull(mViewClassName); - } + String getViewClass(); - public final boolean hasUpdates() { - return mNodeUpdated || hasNewLayout() || isDirty(); - } + boolean hasUpdates(); - public final void markUpdateSeen() { - mNodeUpdated = false; - if (hasNewLayout()) { - markLayoutSeen(); - } - } + void markUpdateSeen(); - public void markUpdated() { - if (mNodeUpdated) { - return; - } - mNodeUpdated = true; - ReactShadowNode parent = getParent(); - if (parent != null) { - parent.markUpdated(); - } - } + void markUpdated(); - public final boolean hasUnseenUpdates() { - return mNodeUpdated; - } + boolean hasUnseenUpdates(); - public void dirty() { - if (!isVirtual()) { - mYogaNode.dirty(); - } - } + void dirty(); - public final boolean isDirty() { - return mYogaNode != null && mYogaNode.isDirty(); - } + boolean isDirty(); - 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; + void addChildAt(T child, 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(); + T removeChildAt(int i); - int increase = child.mIsLayoutOnly ? child.mTotalNativeChildren : 1; - mTotalNativeChildren += increase; + int getChildCount(); - updateNativeChildrenCountInParent(increase); - } + T getChildAt(int i); - 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; + int indexOf(T child); - 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(); - } - } - } + void removeAndDisposeAllChildren(); /** * 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()}). */ - public void onBeforeLayout() { - } + void onBeforeLayout(); - public final void updateProperties(ReactStylesDiffMap props) { - ViewManagerPropertyUpdater.updateProps(this, props); - onAfterUpdateTransaction(); - } + void updateProperties(ReactStylesDiffMap props); - public void onAfterUpdateTransaction() { - // no-op - } + void onAfterUpdateTransaction(); /** * 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 */ - public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { - } + 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) { - if (mNodeUpdated) { - onCollectExtraUpdates(uiViewOperationQueue); - } + NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); - 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 getReactTag(); - int newScreenX = Math.round(layoutX); - int newScreenY = Math.round(layoutY); - int newScreenWidth = newAbsoluteRight - newAbsoluteLeft; - int newScreenHeight = newAbsoluteBottom - newAbsoluteTop; + void setReactTag(int reactTag); - boolean layoutHasChanged = - newScreenX != mScreenX || - newScreenY != mScreenY || - newScreenWidth != mScreenWidth || - newScreenHeight != mScreenHeight; + T getRootNode(); - mScreenX = newScreenX; - mScreenY = newScreenY; - mScreenWidth = newScreenWidth; - mScreenHeight = newScreenHeight; + void setRootNode(T rootNode); - if (layoutHasChanged) { - nativeViewHierarchyOptimizer.handleUpdateLayout(this); - } + void setViewClassName(String viewClassName); - 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; - } + @Nullable + T getParent(); /** * 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. */ - public final ThemedReactContext getThemedContext() { - return Assertions.assertNotNull(mThemedContext); - } + ThemedReactContext getThemedContext(); - public void setThemedContext(ThemedReactContext themedContext) { - mThemedContext = themedContext; - } + void setThemedContext(ThemedReactContext themedContext); - public final boolean shouldNotifyOnLayout() { - return mShouldNotifyOnLayout; - } + boolean shouldNotifyOnLayout(); - public void calculateLayout() { - mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); - } + void calculateLayout(); - public final boolean hasNewLayout() { - return mYogaNode != null && mYogaNode.hasNewLayout(); - } + boolean hasNewLayout(); - public final void markLayoutSeen() { - if (mYogaNode != null) { - mYogaNode.markLayoutSeen(); - } - } + void markLayoutSeen(); /** * Adds a child that the native view hierarchy will have at this index in the native view * corresponding to this node. */ - public final void addNativeChildAt(ReactShadowNode child, int nativeIndex) { - Assertions.assertCondition(!mIsLayoutOnly); - Assertions.assertCondition(!child.mIsLayoutOnly); + void addNativeChildAt(T child, int nativeIndex); - if (mNativeChildren == null) { - mNativeChildren = new ArrayList<>(4); - } + T removeNativeChildAt(int i); - mNativeChildren.add(nativeIndex, child); - child.mNativeParent = this; - } + void removeAllNativeChildren(); - public final ReactShadowNode removeNativeChildAt(int i) { - Assertions.assertNotNull(mNativeChildren); - ReactShadowNode removed = mNativeChildren.remove(i); - removed.mNativeParent = null; - return removed; - } + int getNativeChildCount(); - public final void removeAllNativeChildren() { - if (mNativeChildren != null) { - for (int i = mNativeChildren.size() - 1; i >= 0; i--) { - mNativeChildren.get(i).mNativeParent = null; - } - mNativeChildren.clear(); - } - } + int indexOfNativeChild(T nativeChild); - 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; - } + @Nullable + T getNativeParent(); /** - * 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. */ - 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; - } + void setIsLayoutOnly(boolean isLayoutOnly); - public final boolean isLayoutOnly() { - return mIsLayoutOnly; - } + boolean isLayoutOnly(); - public final int getTotalNativeChildren() { - return mTotalNativeChildren; - } + int getTotalNativeChildren(); - 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; - } + boolean isDescendantOf(T ancestorNode); /** * Returns the offset within the native children owned by all layout-only nodes in the subtree @@ -488,368 +189,144 @@ public class 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 - * getNativeOffsetForChild(Node 4) => 6 + *

In that case: getNativeOffsetForChild(Node 0) => 0 getNativeOffsetForChild(Node 1) => 1 + * getNativeOffsetForChild(Node 2) => 4 getNativeOffsetForChild(Node 3) => 4 + * + *

getNativeOffsetForChild(Node 4) => 6 */ - 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; - } - - public final float getLayoutX() { - return mYogaNode.getLayoutX(); - } - - public final float getLayoutY() { - return mYogaNode.getLayoutY(); - } - - public final float getLayoutWidth() { - return mYogaNode.getLayoutWidth(); - } - - public final float getLayoutHeight() { - return mYogaNode.getLayoutHeight(); - } - - /** - * @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 - */ - public int getScreenY() { - return mScreenY; - } - - /** - * @return width corrected for rounding to pixels. - */ - public int getScreenWidth() { - return mScreenWidth; - } - - /** - * @return height corrected for rounding to pixels. - */ - public int getScreenHeight() { - return mScreenHeight; - } - - public final YogaDirection getLayoutDirection() { - return mYogaNode.getLayoutDirection(); - } - - public void setLayoutDirection(YogaDirection direction) { - mYogaNode.setDirection(direction); - } - - public final YogaValue getStyleWidth() { - return mYogaNode.getWidth(); - } - - public void setStyleWidth(float widthPx) { - mYogaNode.setWidth(widthPx); - } - - public void setStyleWidthPercent(float percent) { - mYogaNode.setWidthPercent(percent); - } - - public void setStyleWidthAuto() { - mYogaNode.setWidthAuto(); - } - - public void setStyleMinWidth(float widthPx) { - mYogaNode.setMinWidth(widthPx); - } - - public void setStyleMinWidthPercent(float percent) { - mYogaNode.setMinWidthPercent(percent); - } - - public void setStyleMaxWidth(float widthPx) { - mYogaNode.setMaxWidth(widthPx); - } - - public void setStyleMaxWidthPercent(float percent) { - mYogaNode.setMaxWidthPercent(percent); - } - - public final YogaValue getStyleHeight() { - return mYogaNode.getHeight(); - } - - public void setStyleHeight(float heightPx) { - mYogaNode.setHeight(heightPx); - } - - public void setStyleHeightPercent(float percent) { - mYogaNode.setHeightPercent(percent); - } - - public void setStyleHeightAuto() { - mYogaNode.setHeightAuto(); - } - - public void setStyleMinHeight(float widthPx) { - mYogaNode.setMinHeight(widthPx); - } - - public void setStyleMinHeightPercent(float percent) { - mYogaNode.setMinHeightPercent(percent); - } - - public void setStyleMaxHeight(float widthPx) { - mYogaNode.setMaxHeight(widthPx); - } - - public void setStyleMaxHeightPercent(float percent) { - mYogaNode.setMaxHeightPercent(percent); - } - - public void setFlex(float flex) { - mYogaNode.setFlex(flex); - } - - public void setFlexGrow(float flexGrow) { - mYogaNode.setFlexGrow(flexGrow); - } - - public void setFlexShrink(float flexShrink) { - mYogaNode.setFlexShrink(flexShrink); - } - - public void setFlexBasis(float flexBasis) { - mYogaNode.setFlexBasis(flexBasis); - } - - public void setFlexBasisAuto() { - mYogaNode.setFlexBasisAuto(); - } - - public void setFlexBasisPercent(float percent) { - mYogaNode.setFlexBasisPercent(percent); - } - - public void setStyleAspectRatio(float aspectRatio) { - mYogaNode.setAspectRatio(aspectRatio); - } - - public void setFlexDirection(YogaFlexDirection flexDirection) { - mYogaNode.setFlexDirection(flexDirection); - } - - public void setFlexWrap(YogaWrap wrap) { - mYogaNode.setWrap(wrap); - } - - public void setAlignSelf(YogaAlign alignSelf) { - mYogaNode.setAlignSelf(alignSelf); - } - - public void setAlignItems(YogaAlign alignItems) { - mYogaNode.setAlignItems(alignItems); - } - - public void setAlignContent(YogaAlign alignContent) { - mYogaNode.setAlignContent(alignContent); - } - - public void setJustifyContent(YogaJustify justifyContent) { - mYogaNode.setJustifyContent(justifyContent); - } - - public void setOverflow(YogaOverflow overflow) { - mYogaNode.setOverflow(overflow); - } - - public void setDisplay(YogaDisplay display) { - mYogaNode.setDisplay(display); - } - - public void setMargin(int spacingType, float margin) { - mYogaNode.setMargin(YogaEdge.fromInt(spacingType), margin); - } - - public void setMarginPercent(int spacingType, float percent) { - mYogaNode.setMarginPercent(YogaEdge.fromInt(spacingType), percent); - } - - public void setMarginAuto(int spacingType) { - mYogaNode.setMarginAuto(YogaEdge.fromInt(spacingType)); - } - - public final float getPadding(int spacingType) { - return mYogaNode.getLayoutPadding(YogaEdge.fromInt(spacingType)); - } - - public final YogaValue getStylePadding(int spacingType) { - return mYogaNode.getPadding(YogaEdge.fromInt(spacingType)); - } - - public void setDefaultPadding(int spacingType, float padding) { - mDefaultPadding.set(spacingType, padding); - updatePadding(); - } - - public void setPadding(int spacingType, float padding) { - mPadding[spacingType] = padding; - mPaddingIsPercent[spacingType] = false; - updatePadding(); - } - - 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]); - } - } - } - - public void setBorder(int spacingType, float borderWidth) { - mYogaNode.setBorder(YogaEdge.fromInt(spacingType), borderWidth); - } - - public void setPosition(int spacingType, float position) { - mYogaNode.setPosition(YogaEdge.fromInt(spacingType), position); - } - - public void setPositionPercent(int spacingType, float percent) { - mYogaNode.setPositionPercent(YogaEdge.fromInt(spacingType), percent); - } - - public void setPositionType(YogaPositionType positionType) { - mYogaNode.setPositionType(positionType); - } - - public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { - mShouldNotifyOnLayout = shouldNotifyOnLayout; - } - - public void setBaselineFunction(YogaBaselineFunction baselineFunction) { - mYogaNode.setBaselineFunction(baselineFunction); - } - - 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); - } - } + int getNativeOffsetForChild(T child); + + float getLayoutX(); + + float getLayoutY(); + + float getLayoutWidth(); + + float getLayoutHeight(); + + /** @return the x position of the corresponding view on the screen, rounded to pixels */ + int getScreenX(); + + /** @return the y position of the corresponding view on the screen, rounded to pixels */ + int getScreenY(); + + /** @return width corrected for rounding to pixels. */ + int getScreenWidth(); + + /** @return height corrected for rounding to pixels. */ + int getScreenHeight(); + + YogaDirection getLayoutDirection(); + + void setLayoutDirection(YogaDirection direction); + + YogaValue getStyleWidth(); + + void setStyleWidth(float widthPx); + + void setStyleWidthPercent(float percent); + + void setStyleWidthAuto(); + + void setStyleMinWidth(float widthPx); + + void setStyleMinWidthPercent(float percent); + + void setStyleMaxWidth(float widthPx); + + void setStyleMaxWidthPercent(float percent); + + YogaValue getStyleHeight(); + + void setStyleHeight(float heightPx); + + void setStyleHeightPercent(float percent); + + void setStyleHeightAuto(); + + void setStyleMinHeight(float widthPx); + + void setStyleMinHeightPercent(float percent); + + void setStyleMaxHeight(float widthPx); + + void setStyleMaxHeightPercent(float percent); + + void setFlex(float flex); + + void setFlexGrow(float flexGrow); + + void setFlexShrink(float flexShrink); + + void setFlexBasis(float flexBasis); + + void setFlexBasisAuto(); + + void setFlexBasisPercent(float percent); + + void setStyleAspectRatio(float aspectRatio); + + void setFlexDirection(YogaFlexDirection flexDirection); + + void setFlexWrap(YogaWrap wrap); + + void setAlignSelf(YogaAlign alignSelf); + + void setAlignItems(YogaAlign alignItems); + + void setAlignContent(YogaAlign alignContent); + + void setJustifyContent(YogaJustify justifyContent); + + void setOverflow(YogaOverflow overflow); + + void setDisplay(YogaDisplay display); + + void setMargin(int spacingType, float margin); + + void setMarginPercent(int spacingType, float percent); + + void setMarginAuto(int spacingType); + + float getPadding(int spacingType); + + YogaValue getStylePadding(int spacingType); + + void setDefaultPadding(int spacingType, float padding); + + void setPadding(int spacingType, float padding); + + void setPaddingPercent(int spacingType, float percent); + + void setBorder(int spacingType, float borderWidth); + + void setPosition(int spacingType, float position); + + void setPositionPercent(int spacingType, float percent); + + void setPositionType(YogaPositionType positionType); + + void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout); + + void setBaselineFunction(YogaBaselineFunction baselineFunction); + + void setMeasureFunction(YogaMeasureFunction measureFunction); + + boolean isMeasureDefined(); + + void dispose(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java new file mode 100644 index 0000000000..b82478140e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -0,0 +1,937 @@ +/** + * 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.react.uimanager.annotations.ReactPropertyHolder; +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. + */ +@ReactPropertyHolder +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 8dc1f37c83..9813184b46 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -109,7 +109,7 @@ public class UIImplementation { } protected ReactShadowNode createRootShadowNode() { - ReactShadowNode rootCSSNode = new ReactShadowNode(); + ReactShadowNode rootCSSNode = new ReactShadowNodeImpl(); 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 cfdd34be26..ca9fc7c6c2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -2,15 +2,7 @@ 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; @@ -18,6 +10,11 @@ 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 @@ -333,7 +330,7 @@ import com.facebook.react.uimanager.annotations.ReactPropGroup; */ /*package*/ static Map getNativePropSettersForShadowNodeClass( Class cls) { - if (cls == ReactShadowNode.class) { + if (cls == ReactShadowNodeImpl.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 70dc6faaf2..ac9739b02d 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,23 +9,21 @@ 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 com.facebook.react.uimanager.ReactShadowNode; +import javax.annotation.Nullable; /** * Base class for ARTView virtual nodes: {@link ARTGroupShadowNode}, {@link ARTShapeShadowNode} and * indirectly for {@link ARTTextShadowNode}. */ -public abstract class ARTVirtualNode extends ReactShadowNode { +public abstract class ARTVirtualNode extends ReactShadowNodeImpl { 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 52f3c22510..8c3b5fde63 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,9 +10,8 @@ package com.facebook.react.views.modal; import android.graphics.Point; - import com.facebook.react.uimanager.LayoutShadowNode; -import com.facebook.react.uimanager.ReactShadowNode; +import com.facebook.react.uimanager.ReactShadowNodeImpl; /** * We implement the Modal by using an Android Dialog. That will fill the entire window of the @@ -26,10 +25,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(ReactShadowNode child, int i) { + public void addChildAt(ReactShadowNodeImpl 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 b97a5bf544..6cf0200c74 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,16 +9,15 @@ 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 ReactShadowNode { +public class ReactRawTextShadowNode extends ReactShadowNodeImpl { @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 169a001dbb..3022fb19fd 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java @@ -9,27 +9,25 @@ 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 @@ -61,7 +59,7 @@ public class ReactPropForShadowNodeSetterTest { return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues)); } - private class ShadowViewUnderTest extends ReactShadowNode { + private class ShadowViewUnderTest extends ReactShadowNodeImpl { 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 1ddcdf047c..8b9717dd7e 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java @@ -9,15 +9,12 @@ 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 org.junit.Test; +import java.util.Map; 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; @@ -69,55 +66,61 @@ public class ReactPropForShadowNodeSpecTest { @Test(expected = RuntimeException.class) public void testMethodWithWrongNumberOfParams() { - new BaseViewManager(new ReactShadowNode() { - @ReactProp(name = "prop") - public void setterWithIncorrectNumberOfArgs(boolean value, int anotherValue) { - } - }.getClass()).getNativeProps(); + new BaseViewManager( + new ReactShadowNodeImpl() { + @ReactProp(name = "prop") + public void setterWithIncorrectNumberOfArgs(boolean value, int anotherValue) {} + }.getClass()) + .getNativeProps(); } @Test(expected = RuntimeException.class) public void testMethodWithTooFewParams() { - new BaseViewManager(new ReactShadowNode() { - @ReactProp(name = "prop") - public void setterWithNoArgs() { - } - }.getClass()).getNativeProps(); + new BaseViewManager( + new ReactShadowNodeImpl() { + @ReactProp(name = "prop") + public void setterWithNoArgs() {} + }.getClass()) + .getNativeProps(); } @Test(expected = RuntimeException.class) public void testUnsupportedValueType() { - new BaseViewManager(new ReactShadowNode() { - @ReactProp(name = "prop") - public void setterWithMap(Map value) { - } - }.getClass()).getNativeProps(); + new BaseViewManager( + new ReactShadowNodeImpl() { + @ReactProp(name = "prop") + public void setterWithMap(Map value) {} + }.getClass()) + .getNativeProps(); } @Test(expected = RuntimeException.class) public void testGroupInvalidNumberOfParams() { - new BaseViewManager(new ReactShadowNode() { - @ReactPropGroup(names = {"prop1", "prop2"}) - public void setterWithTooManyParams(int index, float value, boolean bool) { - } - }.getClass()).getNativeProps(); + new BaseViewManager( + new ReactShadowNodeImpl() { + @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 ReactShadowNode() { - @ReactPropGroup(names = {"prop1", "prop2"}) - public void setterWithTooManyParams(int index) { - } - }.getClass()).getNativeProps(); + new BaseViewManager( + new ReactShadowNodeImpl() { + @ReactPropGroup(names = {"prop1", "prop2"}) + public void setterWithTooManyParams(int index) {} + }.getClass()) + .getNativeProps(); } @Test(expected = RuntimeException.class) public void testGroupNoIndexParam() { - new BaseViewManager(new ReactShadowNode() { - @ReactPropGroup(names = {"prop1", "prop2"}) - public void setterWithTooManyParams(float value, boolean bool) { - } - }.getClass()).getNativeProps(); + new BaseViewManager( + new ReactShadowNodeImpl() { + @ReactPropGroup(names = {"prop1", "prop2"}) + public void setterWithTooManyParams(float value, boolean bool) {} + }.getClass()) + .getNativeProps(); } }