/**
* Copyright (c) 2017-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.litho;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.DimenRes;
import android.support.annotation.Dimension;
import android.support.annotation.DrawableRes;
import android.support.annotation.Px;
import android.support.annotation.StringRes;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.SparseArray;
import com.facebook.infer.annotation.ReturnsOwnership;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.litho.reference.DrawableReference;
import com.facebook.litho.reference.Reference;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaBaselineFunction;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
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.YogaPositionType;
import com.facebook.yoga.YogaWrap;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.support.annotation.Dimension.DP;
import static com.facebook.litho.ComponentContext.NULL_LAYOUT;
import static com.facebook.yoga.YogaEdge.ALL;
import static com.facebook.yoga.YogaEdge.BOTTOM;
import static com.facebook.yoga.YogaEdge.END;
import static com.facebook.yoga.YogaEdge.HORIZONTAL;
import static com.facebook.yoga.YogaEdge.LEFT;
import static com.facebook.yoga.YogaEdge.RIGHT;
import static com.facebook.yoga.YogaEdge.START;
import static com.facebook.yoga.YogaEdge.TOP;
import static com.facebook.yoga.YogaEdge.VERTICAL;
/**
* Internal class representing both a {@link ComponentLayout} and a
* {@link com.facebook.litho.ComponentLayout.ContainerBuilder}.
*/
@ThreadConfined(ThreadConfined.ANY)
class InternalNode implements ComponentLayout, ComponentLayout.ContainerBuilder {
// Used to check whether or not the framework can use style IDs for
// paddingStart/paddingEnd due to a bug in some Android devices.
private static final boolean SUPPORTS_RTL = (SDK_INT >= JELLY_BEAN_MR1);
// When this flag is set, layoutDirection style was explicitly set on this node.
private static final long PFLAG_LAYOUT_DIRECTION_IS_SET = 1L << 0;
// When this flag is set, alignSelf was explicitly set on this node.
private static final long PFLAG_ALIGN_SELF_IS_SET = 1L << 1;
// When this flag is set, position type was explicitly set on this node.
private static final long PFLAG_POSITION_TYPE_IS_SET = 1L << 2;
// When this flag is set, flex was explicitly set on this node.
private static final long PFLAG_FLEX_IS_SET = 1L << 3;
// When this flag is set, flex grow was explicitly set on this node.
private static final long PFLAG_FLEX_GROW_IS_SET = 1L << 4;
// When this flag is set, flex shrink was explicitly set on this node.
private static final long PFLAG_FLEX_SHRINK_IS_SET = 1L << 5;
// When this flag is set, flex basis was explicitly set on this node.
private static final long PFLAG_FLEX_BASIS_IS_SET = 1L << 6;
// When this flag is set, importantForAccessibility was explicitly set on this node.
private static final long PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET = 1L << 7;
// When this flag is set, duplicateParentState was explicitly set on this node.
private static final long PFLAG_DUPLICATE_PARENT_STATE_IS_SET = 1L << 8;
// When this flag is set, margin was explicitly set on this node.
private static final long PFLAG_MARGIN_IS_SET = 1L << 9;
// When this flag is set, padding was explicitly set on this node.
private static final long PFLAG_PADDING_IS_SET = 1L << 10;
// When this flag is set, position was explicitly set on this node.
private static final long PFLAG_POSITION_IS_SET = 1L << 11;
// When this flag is set, width was explicitly set on this node.
private static final long PFLAG_WIDTH_IS_SET = 1L << 12;
// When this flag is set, minWidth was explicitly set on this node.
private static final long PFLAG_MIN_WIDTH_IS_SET = 1L << 13;
// When this flag is set, maxWidth was explicitly set on this node.
private static final long PFLAG_MAX_WIDTH_IS_SET = 1L << 14;
// When this flag is set, height was explicitly set on this node.
private static final long PFLAG_HEIGHT_IS_SET = 1L << 15;
// When this flag is set, minHeight was explicitly set on this node.
private static final long PFLAG_MIN_HEIGHT_IS_SET = 1L << 16;
// When this flag is set, maxHeight was explicitly set on this node.
private static final long PFLAG_MAX_HEIGHT_IS_SET = 1L << 17;
// When this flag is set, background was explicitly set on this node.
private static final long PFLAG_BACKGROUND_IS_SET = 1L << 18;
// When this flag is set, foreground was explicitly set on this node.
private static final long PFLAG_FOREGROUND_IS_SET = 1L << 19;
// When this flag is set, visibleHandler was explicitly set on this node.
private static final long PFLAG_VISIBLE_HANDLER_IS_SET = 1L << 20;
// When this flag is set, focusedHandler was explicitly set on this node.
private static final long PFLAG_FOCUSED_HANDLER_IS_SET = 1L << 21;
// When this flag is set, fullImpressionHandler was explicitly set on this node.
private static final long PFLAG_FULL_IMPRESSION_HANDLER_IS_SET = 1L << 22;
// When this flag is set, invisibleHandler was explicitly set on this node.
private static final long PFLAG_INVISIBLE_HANDLER_IS_SET = 1L << 23;
// When this flag is set, unfocusedHandler was explicitly set on this node.
private static final long PFLAG_UNFOCUSED_HANDLER_IS_SET = 1L << 24;
// When this flag is set, touch expansion was explicitly set on this node.
private static final long PFLAG_TOUCH_EXPANSION_IS_SET = 1L << 25;
// When this flag is set, border width was explicitly set on this node.
private static final long PFLAG_BORDER_WIDTH_IS_SET = 1L << 26;
// When this flag is set, aspectRatio was explicitly set on this node.
private static final long PFLAG_ASPECT_RATIO_IS_SET = 1L << 27;
// When this flag is set, transitionKey was explicitly set on this node.
private static final long PFLAG_TRANSITION_KEY_IS_SET = 1L << 28;
// When this flag is set, border color was explicitly set on this node.
private static final long PFLAG_BORDER_COLOR_IS_SET = 1L << 29;
private final ResourceResolver mResourceResolver = new ResourceResolver();
YogaNode mYogaNode;
private ComponentContext mComponentContext;
private Resources mResources;
@ThreadConfined(ThreadConfined.ANY)
private List<Component> mComponents = new ArrayList(1);
private int mImportantForAccessibility = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
private boolean mDuplicateParentState;
private boolean mIsNestedTreeHolder;
private InternalNode mNestedTree;
private InternalNode mNestedTreeHolder;
private long mPrivateFlags;
private Reference<? extends Drawable> mBackground;
private Drawable mForeground;
private int mBorderColor = Color.TRANSPARENT;
private NodeInfo mNodeInfo;
private boolean mForceViewWrapping;
private String mTransitionKey;
private float mVisibleRatio;
private EventHandler<VisibleEvent> mVisibleHandler;
private EventHandler<FocusedVisibleEvent> mFocusedHandler;
private EventHandler<UnfocusedVisibleEvent> mUnfocusedHandler;
private EventHandler<FullImpressionVisibleEvent> mFullImpressionHandler;
private EventHandler<InvisibleEvent> mInvisibleHandler;
private String mTestKey;
private Edges mTouchExpansion;
private Edges mNestedTreePadding;
private Edges mNestedTreeBorderWidth;
private boolean[] mIsPaddingPercent;
private float mResolvedTouchExpansionLeft = YogaConstants.UNDEFINED;
private float mResolvedTouchExpansionRight = YogaConstants.UNDEFINED;
private float mResolvedX = YogaConstants.UNDEFINED;
private float mResolvedY = YogaConstants.UNDEFINED;
private float mResolvedWidth = YogaConstants.UNDEFINED;
private float mResolvedHeight = YogaConstants.UNDEFINED;
private int mLastWidthSpec = DiffNode.UNSPECIFIED;
private int mLastHeightSpec = DiffNode.UNSPECIFIED;
private float mLastMeasuredWidth = DiffNode.UNSPECIFIED;
private float mLastMeasuredHeight = DiffNode.UNSPECIFIED;
private DiffNode mDiffNode;
private boolean mCachedMeasuresValid;
private TreeProps mPendingTreeProps;
void init(YogaNode yogaNode, ComponentContext componentContext, Resources resources) {
yogaNode.setData(this);
mYogaNode = yogaNode;
mComponentContext = componentContext;
mResources = resources;
mResourceResolver.init(
mComponentContext,
componentContext.getResourceCache());
}
@Px
@Override
public int getX() {
if (YogaConstants.isUndefined(mResolvedX)) {
mResolvedX = mYogaNode.getLayoutX();
}
return (int) mResolvedX;
}
@Px
@Override
public int getY() {
if (YogaConstants.isUndefined(mResolvedY)) {
mResolvedY = mYogaNode.getLayoutY();
}
return (int) mResolvedY;
}
@Px
@Override
public int getWidth() {
if (YogaConstants.isUndefined(mResolvedWidth)) {
mResolvedWidth = mYogaNode.getLayoutWidth();
}
return (int) mResolvedWidth;
}
@Px
@Override
public int getHeight() {
if (YogaConstants.isUndefined(mResolvedHeight)) {
mResolvedHeight = mYogaNode.getLayoutHeight();
}
return (int) mResolvedHeight;
}
@Px
@Override
public int getPaddingLeft() {
return FastMath.round(mYogaNode.getLayoutPadding(LEFT));
}
@Px
@Override
public int getPaddingTop() {
return FastMath.round(mYogaNode.getLayoutPadding(TOP));
}
@Px
@Override
public int getPaddingRight() {
return FastMath.round(mYogaNode.getLayoutPadding(RIGHT));
}
@Px
@Override
public int getPaddingBottom() {
return FastMath.round(mYogaNode.getLayoutPadding(BOTTOM));
}
public Reference<? extends Drawable> getBackground() {
return mBackground;
}
public Drawable getForeground() {
return mForeground;
}
public void setCachedMeasuresValid(boolean valid) {
mCachedMeasuresValid = valid;
}
public int getLastWidthSpec() {
return mLastWidthSpec;
}
public void setLastWidthSpec(int widthSpec) {
mLastWidthSpec = widthSpec;
}
public int getLastHeightSpec() {
return mLastHeightSpec;
}
public void setLastHeightSpec(int heightSpec) {
mLastHeightSpec = heightSpec;
}
public boolean hasVisibilityHandlers() {
return mVisibleHandler != null
|| mFocusedHandler != null
|| mUnfocusedHandler != null
|| mFullImpressionHandler != null
|| mInvisibleHandler != null;
}
/**
* The last value the measure funcion associated with this node {@link Component} returned
* for the width. This is used together with {@link InternalNode#getLastWidthSpec()}
* to implement measure caching.
*/
float getLastMeasuredWidth() {
return mLastMeasuredWidth;
}
/**
* Sets the last value the measure funcion associated with this node {@link Component} returned
* for the width.
*/
void setLastMeasuredWidth(float lastMeasuredWidth) {
mLastMeasuredWidth = lastMeasuredWidth;
}
/**
* The last value the measure funcion associated with this node {@link Component} returned
* for the height. This is used together with {@link InternalNode#getLastHeightSpec()}
* to implement measure caching.
*/
float getLastMeasuredHeight() {
return mLastMeasuredHeight;
}
/**
* Sets the last value the measure funcion associated with this node {@link Component} returned
* for the height.
*/
void setLastMeasuredHeight(float lastMeasuredHeight) {
mLastMeasuredHeight = lastMeasuredHeight;
}
DiffNode getDiffNode() {
return mDiffNode;
}
boolean areCachedMeasuresValid() {
return mCachedMeasuresValid;
}
void setDiffNode(DiffNode diffNode) {
mDiffNode = diffNode;
}
/**
* Mark this node as a nested tree root holder.
*/
void markIsNestedTreeHolder(TreeProps currentTreeProps) {
mIsNestedTreeHolder = true;
mPendingTreeProps = TreeProps.copy(currentTreeProps);
}
/**
* @return Whether this node is holding a nested tree or not. The decision was made during
* tree creation {@link ComponentLifecycle#createLayout(ComponentContext, Component, boolean)}.
*/
boolean isNestedTreeHolder() {
return mIsNestedTreeHolder;
}
@Override
public YogaDirection getResolvedLayoutDirection() {
return mYogaNode.getLayoutDirection();
}
@Override
public InternalNode layoutDirection(YogaDirection direction) {
mPrivateFlags |= PFLAG_LAYOUT_DIRECTION_IS_SET;
mYogaNode.setDirection(direction);
return this;
}
InternalNode flexDirection(YogaFlexDirection direction) {
mYogaNode.setFlexDirection(direction);
return this;
}
@Override
public InternalNode wrap(YogaWrap wrap) {
mYogaNode.setWrap(wrap);
return this;
}
@Override
public InternalNode justifyContent(YogaJustify justifyContent) {
mYogaNode.setJustifyContent(justifyContent);
return this;
}
@Override
public InternalNode alignItems(YogaAlign alignItems) {
mYogaNode.setAlignItems(alignItems);
return this;
}
@Override
public InternalNode alignContent(YogaAlign alignContent) {
mYogaNode.setAlignContent(alignContent);
return this;
}
@Override
public InternalNode alignSelf(YogaAlign alignSelf) {
mPrivateFlags |= PFLAG_ALIGN_SELF_IS_SET;
mYogaNode.setAlignSelf(alignSelf);
return this;
}
@Override
public InternalNode positionType(YogaPositionType positionType) {
mPrivateFlags |= PFLAG_POSITION_TYPE_IS_SET;
mYogaNode.setPositionType(positionType);
return this;
}
@Override
public InternalNode flex(float flex) {
mPrivateFlags |= PFLAG_FLEX_IS_SET;
mYogaNode.setFlex(flex);
return this;
}
@Override
public InternalNode flexGrow(float flexGrow) {
mPrivateFlags |= PFLAG_FLEX_GROW_IS_SET;
mYogaNode.setFlexGrow(flexGrow);
return this;
}
@Override
public InternalNode flexShrink(float flexShrink) {
mPrivateFlags |= PFLAG_FLEX_SHRINK_IS_SET;
mYogaNode.setFlexShrink(flexShrink);
return this;
}
@Override
public InternalNode flexBasisPx(@Px int flexBasis) {
mPrivateFlags |= PFLAG_FLEX_BASIS_IS_SET;
mYogaNode.setFlexBasis(flexBasis);
return this;
}
// Used by stetho to re-set auto value
InternalNode flexBasisAuto() {
mYogaNode.setFlexBasisAuto();
return this;
}
@Override
public InternalNode flexBasisPercent(float percent) {
mPrivateFlags |= PFLAG_FLEX_BASIS_IS_SET;
mYogaNode.setFlexBasisPercent(percent);
return this;
}
@Override
public InternalNode flexBasisAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return flexBasisPx(mResourceResolver.resolveDimenOffsetAttr(resId, defaultResId));
}
@Override
public InternalNode flexBasisAttr(@AttrRes int resId) {
return flexBasisAttr(resId, 0);
}
@Override
public InternalNode flexBasisRes(@DimenRes int resId) {
return flexBasisPx(mResourceResolver.resolveDimenOffsetRes(resId));
}
@Override
public InternalNode flexBasisDip(@Dimension(unit = DP) int flexBasis) {
return flexBasisPx(mResourceResolver.dipsToPixels(flexBasis));
}
@Override
public InternalNode importantForAccessibility(int importantForAccessibility) {
mPrivateFlags |= PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET;
mImportantForAccessibility = importantForAccessibility;
return this;
}
@Override
public InternalNode duplicateParentState(boolean duplicateParentState) {
mPrivateFlags |= PFLAG_DUPLICATE_PARENT_STATE_IS_SET;
mDuplicateParentState = duplicateParentState;
return this;
}
@Override
public InternalNode marginPx(YogaEdge edge, @Px int margin) {
mPrivateFlags |= PFLAG_MARGIN_IS_SET;
mYogaNode.setMargin(edge, margin);
return this;
}
@Override
public InternalNode marginPercent(YogaEdge edge, float percent) {
mPrivateFlags |= PFLAG_MARGIN_IS_SET;
mYogaNode.setMarginPercent(edge, percent);
return this;
}
@Override
public InternalNode marginAuto(YogaEdge edge) {
mPrivateFlags |= PFLAG_MARGIN_IS_SET;
mYogaNode.setMarginAuto(edge);
return this;
}
@Override
public InternalNode marginAttr(
YogaEdge edge,
@AttrRes int resId,
@DimenRes int defaultResId) {
return marginPx(edge, mResourceResolver.resolveDimenOffsetAttr(resId, defaultResId));
}
@Override
public InternalNode marginAttr(
YogaEdge edge,
@AttrRes int resId) {
return marginAttr(edge, resId, 0);
}
@Override
public InternalNode marginRes(YogaEdge edge, @DimenRes int resId) {
return marginPx(edge, mResourceResolver.resolveDimenOffsetRes(resId));
}
@Override
public InternalNode marginDip(YogaEdge edge, @Dimension(unit = DP) int margin) {
return marginPx(edge, mResourceResolver.dipsToPixels(margin));
}
@ReturnsOwnership
private Edges getNestedTreePadding() {
if (mNestedTreePadding == null) {
mNestedTreePadding = ComponentsPools.acquireEdges();
}
return mNestedTreePadding;
}
@Override
public InternalNode paddingPx(YogaEdge edge, @Px int padding) {
mPrivateFlags |= PFLAG_PADDING_IS_SET;
if (mIsNestedTreeHolder) {
getNestedTreePadding().set(edge, padding);
setIsPaddingPercent(edge, false);
} else {
mYogaNode.setPadding(edge, padding);
}
return this;
}
@Override
public InternalNode paddingPercent(YogaEdge edge, float percent) {
mPrivateFlags |= PFLAG_PADDING_IS_SET;
if (mIsNestedTreeHolder) {
getNestedTreePadding().set(edge, percent);
setIsPaddingPercent(edge, true);
} else {
mYogaNode.setPaddingPercent(edge, percent);
}
return this;
}
@Override
public InternalNode paddingAttr(
YogaEdge edge,
@AttrRes int resId,
@DimenRes int defaultResId) {
return paddingPx(edge, mResourceResolver.resolveDimenOffsetAttr(resId, defaultResId));
}
@Override
public InternalNode paddingAttr(
YogaEdge edge,
@AttrRes int resId) {
return paddingAttr(edge, resId, 0);
}
@Override
public InternalNode paddingRes(YogaEdge edge, @DimenRes int resId) {
return paddingPx(edge, mResourceResolver.resolveDimenOffsetRes(resId));
}
@Override
public InternalNode paddingDip(YogaEdge edge, @Dimension(unit = DP) int padding) {
return paddingPx(edge, mResourceResolver.dipsToPixels(padding));
}
@Override
public InternalNode borderWidthPx(YogaEdge edge, @Px int borderWidth) {
mPrivateFlags |= PFLAG_BORDER_WIDTH_IS_SET;
if (mIsNestedTreeHolder) {
if (mNestedTreeBorderWidth == null) {
mNestedTreeBorderWidth = ComponentsPools.acquireEdges();
}
mNestedTreeBorderWidth.set(edge, borderWidth);
} else {
mYogaNode.setBorder(edge, borderWidth);
}
return this;
}
@Override
public InternalNode borderWidthAttr(
YogaEdge edge,
@AttrRes int resId,
@DimenRes int defaultResId) {
return borderWidthPx(edge, mResourceResolver.resolveDimenOffsetAttr(resId, defaultResId));
}
@Override
public InternalNode borderWidthAttr(
YogaEdge edge,
@AttrRes int resId) {
return borderWidthAttr(edge, resId, 0);
}
@Override
public InternalNode borderWidthRes(YogaEdge edge, @DimenRes int resId) {
return borderWidthPx(edge, mResourceResolver.resolveDimenOffsetRes(resId));
}
@Override
public InternalNode borderWidthDip(
YogaEdge edge,
@Dimension(unit = DP) int borderWidth) {
return borderWidthPx(edge, mResourceResolver.dipsToPixels(borderWidth));
}
@Override
public Builder borderColor(@ColorInt int borderColor) {
mPrivateFlags |= PFLAG_BORDER_COLOR_IS_SET;
mBorderColor = borderColor;
return this;
}
@Override
public InternalNode positionPx(YogaEdge edge, @Px int position) {
mPrivateFlags |= PFLAG_POSITION_IS_SET;
mYogaNode.setPosition(edge, position);
return this;
}
@Override
public InternalNode positionPercent(YogaEdge edge, float percent) {
mPrivateFlags |= PFLAG_POSITION_IS_SET;
mYogaNode.setPositionPercent(edge, percent);
return this;
}
@Override
public InternalNode positionAttr(
YogaEdge edge,
@AttrRes int resId,
@DimenRes int defaultResId) {
return positionPx(edge, mResourceResolver.resolveDimenOffsetAttr(resId, defaultResId));
}
@Override
public InternalNode positionAttr(YogaEdge edge, @AttrRes int resId) {
return positionAttr(edge, resId, 0);
}
@Override
public InternalNode positionRes(YogaEdge edge, @DimenRes int resId) {
return positionPx(edge, mResourceResolver.resolveDimenOffsetRes(resId));
}
@Override
public InternalNode positionDip(
YogaEdge edge,
@Dimension(unit = DP) int position) {
return positionPx(edge, mResourceResolver.dipsToPixels(position));
}
@Override
public InternalNode widthPx(@Px int width) {
mPrivateFlags |= PFLAG_WIDTH_IS_SET;
mYogaNode.setWidth(width);
return this;
}
// Used by stetho to re-set auto value
InternalNode widthAuto() {
mYogaNode.setWidthAuto();
return this;
}
@Override
public InternalNode widthPercent(float percent) {
mPrivateFlags |= PFLAG_WIDTH_IS_SET;
mYogaNode.setWidthPercent(percent);
return this;
}
@Override
public InternalNode widthRes(@DimenRes int resId) {
return widthPx(mResourceResolver.resolveDimenSizeRes(resId));
}
@Override
public InternalNode widthAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return widthPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
@Override
public InternalNode widthAttr(@AttrRes int resId) {
return widthAttr(resId, 0);
}
@Override
public InternalNode widthDip(@Dimension(unit = DP) int width) {
return widthPx(mResourceResolver.dipsToPixels(width));
}
@Override
public InternalNode minWidthPx(@Px int minWidth) {
mPrivateFlags |= PFLAG_MIN_WIDTH_IS_SET;
mYogaNode.setMinWidth(minWidth);
return this;
}
@Override
public InternalNode minWidthPercent(float percent) {
mPrivateFlags |= PFLAG_MIN_WIDTH_IS_SET;
mYogaNode.setMinWidthPercent(percent);
return this;
}
@Override
public InternalNode minWidthAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return minWidthPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
@Override
public InternalNode minWidthAttr(@AttrRes int resId) {
return minWidthAttr(resId, 0);
}
@Override
public InternalNode minWidthRes(@DimenRes int resId) {
return minWidthPx(mResourceResolver.resolveDimenSizeRes(resId));
}
@Override
public InternalNode minWidthDip(@Dimension(unit = DP) int minWidth) {
return minWidthPx(mResourceResolver.dipsToPixels(minWidth));
}
@Override
public InternalNode maxWidthPx(@Px int maxWidth) {
mPrivateFlags |= PFLAG_MAX_WIDTH_IS_SET;
mYogaNode.setMaxWidth(maxWidth);
return this;
}
@Override
public InternalNode maxWidthPercent(float percent) {
mPrivateFlags |= PFLAG_MAX_WIDTH_IS_SET;
mYogaNode.setMaxWidthPercent(percent);
return this;
}
@Override
public InternalNode maxWidthAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return maxWidthPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
@Override
public InternalNode maxWidthAttr(@AttrRes int resId) {
return maxWidthAttr(resId, 0);
}
@Override
public InternalNode maxWidthRes(@DimenRes int resId) {
return maxWidthPx(mResourceResolver.resolveDimenSizeRes(resId));
}
@Override
public InternalNode maxWidthDip(@Dimension(unit = DP) int maxWidth) {
return maxWidthPx(mResourceResolver.dipsToPixels(maxWidth));
}
@Override
public InternalNode heightPx(@Px int height) {
mPrivateFlags |= PFLAG_HEIGHT_IS_SET;
mYogaNode.setHeight(height);
return this;
}
// Used by stetho to re-set auto value
InternalNode heightAuto() {
mYogaNode.setHeightAuto();
return this;
}
@Override
public InternalNode heightPercent(float percent) {
mPrivateFlags |= PFLAG_HEIGHT_IS_SET;
mYogaNode.setHeightPercent(percent);
return this;
}
@Override
public InternalNode heightRes(@DimenRes int resId) {
return heightPx(mResourceResolver.resolveDimenSizeRes(resId));
}
@Override
public InternalNode heightAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return heightPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
@Override
public InternalNode heightAttr(@AttrRes int resId) {
return heightAttr(resId, 0);
}
@Override
public InternalNode heightDip(@Dimension(unit = DP) int height) {
return heightPx(mResourceResolver.dipsToPixels(height));
}
@Override
public InternalNode minHeightPx(@Px int minHeight) {
mPrivateFlags |= PFLAG_MIN_HEIGHT_IS_SET;
mYogaNode.setMinHeight(minHeight);
return this;
}
@Override
public InternalNode minHeightPercent(float percent) {
mPrivateFlags |= PFLAG_MIN_HEIGHT_IS_SET;
mYogaNode.setMinHeightPercent(percent);
return this;
}
@Override
public InternalNode minHeightAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return minHeightPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
@Override
public InternalNode minHeightAttr(@AttrRes int resId) {
return minHeightAttr(resId, 0);
}
@Override
public InternalNode minHeightRes(@DimenRes int resId) {
return minHeightPx(mResourceResolver.resolveDimenSizeRes(resId));
}
@Override
public InternalNode minHeightDip(@Dimension(unit = DP) int minHeight) {
return minHeightPx(mResourceResolver.dipsToPixels(minHeight));
}
@Override
public InternalNode maxHeightPx(@Px int maxHeight) {
mPrivateFlags |= PFLAG_MAX_HEIGHT_IS_SET;
mYogaNode.setMaxHeight(maxHeight);
return this;
}
@Override
public InternalNode maxHeightPercent(float percent) {
mPrivateFlags |= PFLAG_MAX_HEIGHT_IS_SET;
mYogaNode.setMaxHeightPercent(percent);
return this;
}
@Override
public InternalNode maxHeightAttr(@AttrRes int resId, @DimenRes int defaultResId) {
return maxHeightPx(mResourceResolver.resolveDimenSizeAttr(resId, defaultResId));
}
@Override
public InternalNode maxHeightAttr(@AttrRes int resId) {
return maxHeightAttr(resId, 0);
}
@Override
public InternalNode maxHeightRes(@DimenRes int resId) {
return maxHeightPx(mResourceResolver.resolveDimenSizeRes(resId));
}
@Override
public InternalNode maxHeightDip(@Dimension(unit = DP) int maxHeight) {
return maxHeightPx(mResourceResolver.dipsToPixels(maxHeight));
}
@Override
public InternalNode aspectRatio(float aspectRatio) {
mPrivateFlags |= PFLAG_ASPECT_RATIO_IS_SET;
mYogaNode.setAspectRatio(aspectRatio);
return this;
}
private boolean shouldApplyTouchExpansion() {
return mTouchExpansion != null && mNodeInfo != null && mNodeInfo.hasTouchEventHandlers();
}
boolean hasTouchExpansion() {
return ((mPrivateFlags & PFLAG_TOUCH_EXPANSION_IS_SET) != 0L);
}
Edges getTouchExpansion() {
return mTouchExpansion;
}
int getTouchExpansionLeft() {
if (!shouldApplyTouchExpansion()) {
return 0;
}
if (YogaConstants.isUndefined(mResolvedTouchExpansionLeft)) {
mResolvedTouchExpansionLeft = resolveHorizontalEdges(mTouchExpansion, YogaEdge.LEFT);
}
return FastMath.round(mResolvedTouchExpansionLeft);
}
int getTouchExpansionTop() {
if (!shouldApplyTouchExpansion()) {
return 0;
}
return FastMath.round(mTouchExpansion.get(YogaEdge.TOP));
}
int getTouchExpansionRight() {
if (!shouldApplyTouchExpansion()) {
return 0;
}
if (YogaConstants.isUndefined(mResolvedTouchExpansionRight)) {
mResolvedTouchExpansionRight = resolveHorizontalEdges(mTouchExpansion, YogaEdge.RIGHT);
}
return FastMath.round(mResolvedTouchExpansionRight);
}
int getTouchExpansionBottom() {
if (!shouldApplyTouchExpansion()) {
return 0;
}
return FastMath.round(mTouchExpansion.get(YogaEdge.BOTTOM));
}
@Override
public InternalNode touchExpansionPx(YogaEdge edge, @Px int touchExpansion) {
if (mTouchExpansion == null) {
mTouchExpansion = ComponentsPools.acquireEdges();
}
mPrivateFlags |= PFLAG_TOUCH_EXPANSION_IS_SET;
mTouchExpansion.set(edge, touchExpansion);
return this;
}
@Override
public InternalNode touchExpansionAttr(
YogaEdge edge,
@AttrRes int resId,
@DimenRes int defaultResId) {
return touchExpansionPx(
edge,
mResourceResolver.resolveDimenOffsetAttr(resId, defaultResId));
}
@Override
public InternalNode touchExpansionAttr(
YogaEdge edge,
@AttrRes int resId) {
return touchExpansionAttr(edge, resId, 0);
}
@Override
public InternalNode touchExpansionRes(YogaEdge edge, @DimenRes int resId) {
return touchExpansionPx(edge, mResourceResolver.resolveDimenOffsetRes(resId));
}
@Override
public InternalNode touchExpansionDip(
YogaEdge edge,
@Dimension(unit = DP) int touchExpansion) {
return touchExpansionPx(edge, mResourceResolver.dipsToPixels(touchExpansion));
}
@Override
public InternalNode child(ComponentLayout child) {
if (child != null && child != NULL_LAYOUT) {
addChildAt((InternalNode) child, mYogaNode.getChildCount());
}
return this;
}
@Override
public InternalNode child(ComponentLayout.Builder child) {
if (child != null && child != NULL_LAYOUT) {
child(child.build());
}
return this;
}
@Override
public InternalNode child(Component<?> child) {
if (child != null) {
child(Layout.create(mComponentContext, child));
}
return this;
}
@Override
public InternalNode child(Component.Builder<?> child) {
if (child != null) {
child(child.build());
}
return this;
}
@Override
public InternalNode background(Reference<? extends Drawable> background) {
mPrivateFlags |= PFLAG_BACKGROUND_IS_SET;
mBackground = background;
setPaddingFromDrawableReference(background);
return this;
}
@Override
public InternalNode background(Reference.Builder<? extends Drawable> builder) {
return background(builder.build());
}
@Override
public InternalNode background(Drawable background) {
return background(DrawableReference.create().drawable(background));
}
@Override
public InternalNode backgroundAttr(@AttrRes int resId, @DrawableRes int defaultResId) {
return backgroundRes(mResourceResolver.resolveResIdAttr(resId, defaultResId));
}
@Override
public InternalNode backgroundAttr(@AttrRes int resId) {
return backgroundAttr(resId, 0);
}
@Override
public InternalNode backgroundRes(@DrawableRes int resId) {
if (resId == 0) {
return background((Drawable) null);
}
return background(mComponentContext.getResources().getDrawable(resId));
}
@Override
public InternalNode backgroundColor(@ColorInt int backgroundColor) {
return background(new ColorDrawable(backgroundColor));
}
@Override
public InternalNode foreground(Drawable foreground) {
mPrivateFlags |= PFLAG_FOREGROUND_IS_SET;
mForeground = foreground;
return this;
}
@Override
public InternalNode foregroundAttr(@AttrRes int resId, @DrawableRes int defaultResId) {
return foregroundRes(mResourceResolver.resolveResIdAttr(resId, defaultResId));
}
@Override
public InternalNode foregroundAttr(@AttrRes int resId) {
return foregroundAttr(resId, 0);
}
@Override
public InternalNode foregroundRes(@DrawableRes int resId) {
if (resId == 0) {
return foreground(null);
}
return foreground(mResources.getDrawable(resId));
}
@Override
public InternalNode foregroundColor(@ColorInt int foregroundColor) {
return foreground(new ColorDrawable(foregroundColor));
}
@Override
public InternalNode wrapInView() {
mForceViewWrapping = true;
return this;
}
boolean isForceViewWrapping() {
return mForceViewWrapping;
}
@Override
public InternalNode clickHandler(EventHandler<ClickEvent> clickHandler) {
getOrCreateNodeInfo().setClickHandler(clickHandler);
return this;
}
@Override
public InternalNode longClickHandler(EventHandler<LongClickEvent> longClickHandler) {
getOrCreateNodeInfo().setLongClickHandler(longClickHandler);
return this;
}
@Override
public InternalNode touchHandler(EventHandler<TouchEvent> touchHandler) {
getOrCreateNodeInfo().setTouchHandler(touchHandler);
return this;
}
@Override
public InternalNode interceptTouchHandler(EventHandler interceptTouchHandler) {
getOrCreateNodeInfo().setInterceptTouchHandler(interceptTouchHandler);
return this;
}
@Override
public ContainerBuilder focusable(boolean isFocusable) {
getOrCreateNodeInfo().setFocusable(isFocusable);
return this;
}
@Override
public ContainerBuilder visibleRatio(float visibleRatio) {
mVisibleRatio = visibleRatio;
return this;
}
float getVisibleRatio() {
return mVisibleRatio;
}
@Override
public InternalNode visibleHandler(EventHandler<VisibleEvent> visibleHandler) {
mPrivateFlags |= PFLAG_VISIBLE_HANDLER_IS_SET;
mVisibleHandler = visibleHandler;
return this;
}
EventHandler<VisibleEvent> getVisibleHandler() {
return mVisibleHandler;
}
@Override
public InternalNode focusedHandler(EventHandler<FocusedVisibleEvent> focusedHandler) {
mPrivateFlags |= PFLAG_FOCUSED_HANDLER_IS_SET;
mFocusedHandler = focusedHandler;
return this;
}
EventHandler<FocusedVisibleEvent> getFocusedHandler() {
return mFocusedHandler;
}
@Override
public InternalNode unfocusedHandler(EventHandler<UnfocusedVisibleEvent> unfocusedHandler) {
mPrivateFlags |= PFLAG_UNFOCUSED_HANDLER_IS_SET;
mUnfocusedHandler = unfocusedHandler;
return this;
}
EventHandler<UnfocusedVisibleEvent> getUnfocusedHandler() {
return mUnfocusedHandler;
}
@Override
public InternalNode fullImpressionHandler(
EventHandler<FullImpressionVisibleEvent> fullImpressionHandler) {
mPrivateFlags |= PFLAG_FULL_IMPRESSION_HANDLER_IS_SET;
mFullImpressionHandler = fullImpressionHandler;
return this;
}
EventHandler<FullImpressionVisibleEvent> getFullImpressionHandler() {
return mFullImpressionHandler;
}
@Override
public InternalNode invisibleHandler(EventHandler<InvisibleEvent> invisibleHandler) {
mPrivateFlags |= PFLAG_INVISIBLE_HANDLER_IS_SET;
mInvisibleHandler = invisibleHandler;
return this;
}
EventHandler<InvisibleEvent> getInvisibleHandler() {
return mInvisibleHandler;
}
@Override
public InternalNode contentDescription(CharSequence contentDescription) {
getOrCreateNodeInfo().setContentDescription(contentDescription);
return this;
}
@Override
public InternalNode contentDescription(@StringRes int stringId) {
return contentDescription(mResources.getString(stringId));
}
@Override
public InternalNode contentDescription(@StringRes int stringId, Object... formatArgs) {
return contentDescription(mResources.getString(stringId, formatArgs));
}
@Override
public InternalNode viewTag(Object viewTag) {
getOrCreateNodeInfo().setViewTag(viewTag);
return this;
}
@Override
public InternalNode viewTags(SparseArray<Object> viewTags) {
getOrCreateNodeInfo().setViewTags(viewTags);
return this;
}
@Override
public InternalNode testKey(String testKey) {
mTestKey = testKey;
return this;
}
@Override
public InternalNode dispatchPopulateAccessibilityEventHandler(
EventHandler<DispatchPopulateAccessibilityEventEvent>
dispatchPopulateAccessibilityEventHandler) {
getOrCreateNodeInfo().setDispatchPopulateAccessibilityEventHandler(
dispatchPopulateAccessibilityEventHandler);
return this;
}
@Override
public InternalNode onInitializeAccessibilityEventHandler(
EventHandler<OnInitializeAccessibilityEventEvent> onInitializeAccessibilityEventHandler) {
getOrCreateNodeInfo().setOnInitializeAccessibilityEventHandler(
onInitializeAccessibilityEventHandler);
return this;
}
@Override
public InternalNode onInitializeAccessibilityNodeInfoHandler(
EventHandler<OnInitializeAccessibilityNodeInfoEvent>
onInitializeAccessibilityNodeInfoHandler) {
getOrCreateNodeInfo().setOnInitializeAccessibilityNodeInfoHandler(
onInitializeAccessibilityNodeInfoHandler);
return this;
}
@Override
public InternalNode onPopulateAccessibilityEventHandler(
EventHandler<OnPopulateAccessibilityEventEvent> onPopulateAccessibilityEventHandler) {
getOrCreateNodeInfo().setOnPopulateAccessibilityEventHandler(
onPopulateAccessibilityEventHandler);
return this;
}
@Override
public InternalNode onRequestSendAccessibilityEventHandler(
EventHandler<OnRequestSendAccessibilityEventEvent> onRequestSendAccessibilityEventHandler) {
getOrCreateNodeInfo().setOnRequestSendAccessibilityEventHandler(
onRequestSendAccessibilityEventHandler);
return this;
}
@Override
public InternalNode performAccessibilityActionHandler(
EventHandler<PerformAccessibilityActionEvent> performAccessibilityActionHandler) {
getOrCreateNodeInfo().setPerformAccessibilityActionHandler(performAccessibilityActionHandler);
return this;
}
@Override
public InternalNode sendAccessibilityEventHandler(
EventHandler<SendAccessibilityEventEvent> sendAccessibilityEventHandler) {
getOrCreateNodeInfo().setSendAccessibilityEventHandler(sendAccessibilityEventHandler);
return this;
}
@Override
public InternalNode sendAccessibilityEventUncheckedHandler(
EventHandler<SendAccessibilityEventUncheckedEvent> sendAccessibilityEventUncheckedHandler) {
getOrCreateNodeInfo().setSendAccessibilityEventUncheckedHandler(
sendAccessibilityEventUncheckedHandler);
return this;
}
@Override
public ContainerBuilder transitionKey(String key) {
if (SDK_INT >= ICE_CREAM_SANDWICH && !TextUtils.isEmpty(key)) {
mPrivateFlags |= PFLAG_TRANSITION_KEY_IS_SET;
mTransitionKey = key;
wrapInView();
}
return this;
}
String getTransitionKey() {
return mTransitionKey;
}
/**
* A unique identifier which may be set for retrieving a component and its bounds when testing.
*/
String getTestKey() {
return mTestKey;
}
void setMeasureFunction(YogaMeasureFunction measureFunction) {
mYogaNode.setMeasureFunction(measureFunction);
}
void setBaselineFunction(YogaBaselineFunction baselineFunction) {
mYogaNode.setBaselineFunction(baselineFunction);
}
boolean hasNewLayout() {
return mYogaNode.hasNewLayout();
}
void markLayoutSeen() {
mYogaNode.markLayoutSeen();
}
float getStyleWidth() {
return mYogaNode.getWidth().value;
}
float getMinWidth() {
return mYogaNode.getMinWidth().value;
}
float getMaxWidth() {
return mYogaNode.getMaxWidth().value;
}
float getStyleHeight() {
return mYogaNode.getHeight().value;
}
float getMinHeight() {
return mYogaNode.getMinHeight().value;
}
float getMaxHeight() {
return mYogaNode.getMaxHeight().value;
}
void calculateLayout(float width, float height) {
if (ComponentsConfiguration.isDebugModeEnabled) {
applyOverridesRecursive(this);
}
mYogaNode.calculateLayout(width, height);
}
private static void applyOverridesRecursive(InternalNode node) {
DebugComponent.getInstance(node, 0).applyOverrides();
for (int i = 0, count = node.getChildCount(); i < count; i++) {
applyOverridesRecursive(node.getChildAt(i));
}
if (node.hasNestedTree()) {
applyOverridesRecursive(node.getNestedTree());
}
}
void calculateLayout() {
calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED);
}
int getChildCount() {
return mYogaNode.getChildCount();
}
com.facebook.yoga.YogaDirection getStyleDirection() {
return mYogaNode.getStyleDirection();
}
InternalNode getChildAt(int index) {
if (mYogaNode.getChildAt(index) == null) {
return null;
}
return (InternalNode) mYogaNode.getChildAt(index).getData();
}
int getChildIndex(InternalNode child) {
for (int i = 0, count = mYogaNode.getChildCount(); i < count; i++) {
if (mYogaNode.getChildAt(i) == child.mYogaNode) {
return i;
}
}
return -1;
}
InternalNode getParent() {
if (mYogaNode == null || mYogaNode.getParent() == null) {
return null;
}
return (InternalNode) mYogaNode.getParent().getData();
}
void addChildAt(InternalNode child, int index) {
mYogaNode.addChildAt(child.mYogaNode, index);
}
InternalNode removeChildAt(int index) {
return (InternalNode) mYogaNode.removeChildAt(index).getData();
}
@Override
public ComponentLayout build() {
return this;
}
private float resolveHorizontalEdges(Edges spacing, YogaEdge edge) {
final boolean isRtl =
(mYogaNode.getLayoutDirection() == YogaDirection.RTL);
final YogaEdge resolvedEdge;
switch (edge) {
case LEFT:
resolvedEdge = (isRtl ? YogaEdge.END : YogaEdge.START);
break;
case RIGHT:
resolvedEdge = (isRtl ? YogaEdge.START : YogaEdge.END);
break;
default:
throw new IllegalArgumentException("Not an horizontal padding edge: " + edge);
}
float result = spacing.getRaw(resolvedEdge);
if (YogaConstants.isUndefined(result)) {
result = spacing.get(edge);
}
return result;
}
ComponentContext getContext() {
return mComponentContext;
}
/**
* Return the list of components contributing to this InternalNode. We have no need for this
* in production but it is useful information to have while debugging. Therefor this list
* will only container the root component if running in production mode.
*/
List<Component> getComponents() {
return mComponents;
}
Component getRootComponent() {
return mComponents.size() == 0 ? null : mComponents.get(0);
}
int getBorderColor() {
return mBorderColor;
}
boolean shouldDrawBorders() {
return mBorderColor != Color.TRANSPARENT
&& (mYogaNode.getLayoutBorder(LEFT) != 0
|| mYogaNode.getLayoutBorder(TOP) != 0
|| mYogaNode.getLayoutBorder(RIGHT) != 0
|| mYogaNode.getLayoutBorder(BOTTOM) != 0);
}
/**
* Set the root component associated with this internal node. This is the component which created
* this internal node. If we are in debug mode we also keep track of any delegate components
* which may have altered anything about this internal node. This is useful when understanding
* the hierarchy of components in the debugger as well as in stetho.
*/
void appendComponent(Component component) {
if (mComponents.size() == 0 || ComponentsConfiguration.isDebugModeEnabled) {
mComponents.add(component);
}
}
boolean hasNestedTree() {
return mNestedTree != null;
}
@Nullable InternalNode getNestedTree() {
return mNestedTree;
}
InternalNode getNestedTreeHolder() {
return mNestedTreeHolder;
}
/**
* Set the nested tree before measuring it in order to transfer over important information
* such as layout direction needed during measurement.
*/
void setNestedTree(InternalNode nestedTree) {
nestedTree.mNestedTreeHolder = this;
mNestedTree = nestedTree;
}
NodeInfo getNodeInfo() {
return mNodeInfo;
}
void copyInto(InternalNode node) {
if (mNodeInfo != null) {
if (node.mNodeInfo == null) {
node.mNodeInfo = mNodeInfo.acquireRef();
} else {
node.mNodeInfo.updateWith(mNodeInfo);
}
}
if ((node.mPrivateFlags & PFLAG_LAYOUT_DIRECTION_IS_SET) == 0L
|| node.getResolvedLayoutDirection() == YogaDirection.INHERIT) {
node.layoutDirection(getResolvedLayoutDirection());
}
if ((node.mPrivateFlags & PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET) == 0L
|| node.mImportantForAccessibility == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
node.mImportantForAccessibility = mImportantForAccessibility;
}
if ((mPrivateFlags & PFLAG_DUPLICATE_PARENT_STATE_IS_SET) != 0L) {
node.mDuplicateParentState = mDuplicateParentState;
}
if ((mPrivateFlags & PFLAG_BACKGROUND_IS_SET) != 0L) {
node.mBackground = mBackground;
}
if ((mPrivateFlags & PFLAG_FOREGROUND_IS_SET) != 0L) {
node.mForeground = mForeground;
}
if (mForceViewWrapping) {
node.mForceViewWrapping = true;
}
if ((mPrivateFlags & PFLAG_VISIBLE_HANDLER_IS_SET) != 0L) {
node.mVisibleHandler = mVisibleHandler;
}
if ((mPrivateFlags & PFLAG_FOCUSED_HANDLER_IS_SET) != 0L) {
node.mFocusedHandler = mFocusedHandler;
}
if ((mPrivateFlags & PFLAG_FULL_IMPRESSION_HANDLER_IS_SET) != 0L) {
node.mFullImpressionHandler = mFullImpressionHandler;
}
if ((mPrivateFlags & PFLAG_INVISIBLE_HANDLER_IS_SET) != 0L) {
node.mInvisibleHandler = mInvisibleHandler;
}
if ((mPrivateFlags & PFLAG_UNFOCUSED_HANDLER_IS_SET) != 0L) {
node.mUnfocusedHandler = mUnfocusedHandler;
}
if (mTestKey != null) {
node.mTestKey = mTestKey;
}
if ((mPrivateFlags & PFLAG_PADDING_IS_SET) != 0L) {
if (mNestedTreePadding == null) {
throw new IllegalStateException("copyInto() must be used when resolving a nestedTree. " +
"If padding was set on the holder node, we must have a mNestedTreePadding instance");
}
final YogaNode yogaNode = node.mYogaNode;
node.mPrivateFlags |= PFLAG_PADDING_IS_SET;
if (isPaddingPercent(LEFT)) {
yogaNode.setPaddingPercent(LEFT, mNestedTreePadding.getRaw(YogaEdge.LEFT));
} else {
yogaNode.setPadding(LEFT, mNestedTreePadding.getRaw(YogaEdge.LEFT));
}
if (isPaddingPercent(TOP)) {
yogaNode.setPaddingPercent(TOP, mNestedTreePadding.getRaw(YogaEdge.TOP));
} else {
yogaNode.setPadding(TOP, mNestedTreePadding.getRaw(YogaEdge.TOP));
}
if (isPaddingPercent(RIGHT)) {
yogaNode.setPaddingPercent(RIGHT, mNestedTreePadding.getRaw(YogaEdge.RIGHT));
} else {
yogaNode.setPadding(RIGHT, mNestedTreePadding.getRaw(YogaEdge.RIGHT));
}
if (isPaddingPercent(BOTTOM)) {
yogaNode.setPaddingPercent(BOTTOM, mNestedTreePadding.getRaw(YogaEdge.BOTTOM));
} else {
yogaNode.setPadding(BOTTOM, mNestedTreePadding.getRaw(YogaEdge.BOTTOM));
}
if (isPaddingPercent(VERTICAL)) {
yogaNode.setPaddingPercent(VERTICAL, mNestedTreePadding.getRaw(YogaEdge.VERTICAL));
} else {
yogaNode.setPadding(VERTICAL, mNestedTreePadding.getRaw(YogaEdge.VERTICAL));
}
if (isPaddingPercent(HORIZONTAL)) {
yogaNode.setPaddingPercent(HORIZONTAL, mNestedTreePadding.getRaw(YogaEdge.HORIZONTAL));
} else {
yogaNode.setPadding(HORIZONTAL, mNestedTreePadding.getRaw(YogaEdge.HORIZONTAL));
}
if (isPaddingPercent(START)) {
yogaNode.setPaddingPercent(START, mNestedTreePadding.getRaw(YogaEdge.START));
} else {
yogaNode.setPadding(START, mNestedTreePadding.getRaw(YogaEdge.START));
}
if (isPaddingPercent(END)) {
yogaNode.setPaddingPercent(END, mNestedTreePadding.getRaw(YogaEdge.END));
} else {
yogaNode.setPadding(END, mNestedTreePadding.getRaw(YogaEdge.END));
}
if (isPaddingPercent(ALL)) {
yogaNode.setPaddingPercent(ALL, mNestedTreePadding.getRaw(YogaEdge.ALL));
} else {
yogaNode.setPadding(ALL, mNestedTreePadding.getRaw(YogaEdge.ALL));
}
}
if ((mPrivateFlags & PFLAG_BORDER_WIDTH_IS_SET) != 0L) {
if (mNestedTreeBorderWidth == null) {
throw new IllegalStateException("copyInto() must be used when resolving a nestedTree. " +
"If border width was set on the holder node, we must have a mNestedTreeBorderWidth " +
"instance");
}
final YogaNode yogaNode = node.mYogaNode;
node.mPrivateFlags |= PFLAG_BORDER_WIDTH_IS_SET;
yogaNode.setBorder(LEFT, mNestedTreeBorderWidth.getRaw(YogaEdge.LEFT));
yogaNode.setBorder(TOP, mNestedTreeBorderWidth.getRaw(YogaEdge.TOP));
yogaNode.setBorder(RIGHT, mNestedTreeBorderWidth.getRaw(YogaEdge.RIGHT));
yogaNode.setBorder(BOTTOM, mNestedTreeBorderWidth.getRaw(YogaEdge.BOTTOM));
yogaNode.setBorder(VERTICAL, mNestedTreeBorderWidth.getRaw(YogaEdge.VERTICAL));
yogaNode.setBorder(HORIZONTAL, mNestedTreeBorderWidth.getRaw(YogaEdge.HORIZONTAL));
yogaNode.setBorder(START, mNestedTreeBorderWidth.getRaw(YogaEdge.START));
yogaNode.setBorder(END, mNestedTreeBorderWidth.getRaw(YogaEdge.END));
yogaNode.setBorder(ALL, mNestedTreeBorderWidth.getRaw(YogaEdge.ALL));
}
if ((mPrivateFlags & PFLAG_TRANSITION_KEY_IS_SET) != 0L) {
node.mTransitionKey = mTransitionKey;
}
if ((mPrivateFlags & PFLAG_BORDER_COLOR_IS_SET) != 0L) {
node.mBorderColor = mBorderColor;
}
if (mVisibleRatio != 0) {
node.mVisibleRatio = mVisibleRatio;
}
}
void setStyleWidthFromSpec(int widthSpec) {
switch (SizeSpec.getMode(widthSpec)) {
case SizeSpec.UNSPECIFIED:
mYogaNode.setWidth(YogaConstants.UNDEFINED);
break;
case SizeSpec.AT_MOST:
mYogaNode.setMaxWidth(SizeSpec.getSize(widthSpec));
break;
case SizeSpec.EXACTLY:
mYogaNode.setWidth(SizeSpec.getSize(widthSpec));
break;
}
}
void setStyleHeightFromSpec(int heightSpec) {
switch (SizeSpec.getMode(heightSpec)) {
case SizeSpec.UNSPECIFIED:
mYogaNode.setHeight(YogaConstants.UNDEFINED);
break;
case SizeSpec.AT_MOST:
mYogaNode.setMaxHeight(SizeSpec.getSize(heightSpec));
break;
case SizeSpec.EXACTLY:
mYogaNode.setHeight(SizeSpec.getSize(heightSpec));
break;
}
}
int getImportantForAccessibility() {
return mImportantForAccessibility;
}
boolean isDuplicateParentStateEnabled() {
return mDuplicateParentState;
}
void applyAttributes(TypedArray a) {
for (int i = 0, size = a.getIndexCount(); i < size; i++) {
final int attr = a.getIndex(i);
if (attr == R.styleable.ComponentLayout_android_layout_width) {
int width = a.getLayoutDimension(attr, -1);
// We don't support WRAP_CONTENT or MATCH_PARENT so no-op for them
if (width >= 0) {
widthPx(width);
}
} else if (attr == R.styleable.ComponentLayout_android_layout_height) {
int height = a.getLayoutDimension(attr, -1);
// We don't support WRAP_CONTENT or MATCH_PARENT so no-op for them
if (height >= 0) {
heightPx(height);
}
} else if (attr == R.styleable.ComponentLayout_android_paddingLeft) {
paddingPx(LEFT, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_paddingTop) {
paddingPx(TOP, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_paddingRight) {
paddingPx(RIGHT, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_paddingBottom) {
paddingPx(BOTTOM, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_paddingStart && SUPPORTS_RTL) {
paddingPx(START, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_paddingEnd && SUPPORTS_RTL) {
paddingPx(END, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_padding) {
paddingPx(ALL, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_marginLeft) {
marginPx(LEFT, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_marginTop) {
marginPx(TOP, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_marginRight) {
marginPx(RIGHT, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_marginBottom) {
marginPx(BOTTOM, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_marginStart && SUPPORTS_RTL) {
marginPx(START, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_marginEnd && SUPPORTS_RTL) {
marginPx(END, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_layout_margin) {
marginPx(ALL, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_importantForAccessibility &&
SDK_INT >= JELLY_BEAN) {
importantForAccessibility(a.getInt(attr, 0));
} else if (attr == R.styleable.ComponentLayout_android_duplicateParentState) {
duplicateParentState(a.getBoolean(attr, false));
} else if (attr == R.styleable.ComponentLayout_android_background) {
if (TypedArrayUtils.isColorAttribute(a, R.styleable.ComponentLayout_android_background)) {
backgroundColor(a.getColor(attr, 0));
} else {
backgroundRes(a.getResourceId(attr, -1));
}
} else if (attr == R.styleable.ComponentLayout_android_foreground) {
if (TypedArrayUtils.isColorAttribute(a, R.styleable.ComponentLayout_android_foreground)) {
foregroundColor(a.getColor(attr, 0));
} else {
foregroundRes(a.getResourceId(attr, -1));
}
} else if (attr == R.styleable.ComponentLayout_android_contentDescription) {
contentDescription(a.getString(attr));
} else if (attr == R.styleable.ComponentLayout_flex_direction) {
flexDirection(YogaFlexDirection.fromInt(a.getInteger(attr, 0)));
} else if (attr == R.styleable.ComponentLayout_flex_wrap) {
wrap(YogaWrap.fromInt(a.getInteger(attr, 0)));
} else if (attr == R.styleable.ComponentLayout_flex_justifyContent) {
justifyContent(YogaJustify.fromInt(a.getInteger(attr, 0)));
} else if (attr == R.styleable.ComponentLayout_flex_alignItems) {
alignItems(YogaAlign.fromInt(a.getInteger(attr, 0)));
} else if (attr == R.styleable.ComponentLayout_flex_alignSelf) {
alignSelf(YogaAlign.fromInt(a.getInteger(attr, 0)));
} else if (attr == R.styleable.ComponentLayout_flex_positionType) {
positionType(YogaPositionType.fromInt(a.getInteger(attr, 0)));
} else if (attr == R.styleable.ComponentLayout_flex) {
final float flex = a.getFloat(attr, -1);
if (flex >= 0f) {
flex(flex);
}
} else if (attr == R.styleable.ComponentLayout_flex_left) {
positionPx(LEFT, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_flex_top) {
positionPx(TOP, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_flex_right) {
positionPx(RIGHT, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_flex_bottom) {
positionPx(BOTTOM, a.getDimensionPixelOffset(attr, 0));
} else if (attr == R.styleable.ComponentLayout_flex_layoutDirection) {
final int layoutDirection = a.getInteger(attr, -1);
layoutDirection(YogaDirection.fromInt(layoutDirection));
}
}
}
/**
* Reset all attributes to default values. Intended to facilitate recycling.
*/
void release() {
if (mYogaNode.getParent() != null || mYogaNode.getChildCount() > 0) {
throw new IllegalStateException("You should not free an attached Internalnode");
}
ComponentsPools.release(mYogaNode);
mYogaNode = null;
mResourceResolver.internalRelease();
mResolvedTouchExpansionLeft = YogaConstants.UNDEFINED;
mResolvedTouchExpansionRight = YogaConstants.UNDEFINED;
mResolvedX = YogaConstants.UNDEFINED;
mResolvedY = YogaConstants.UNDEFINED;
mResolvedWidth = YogaConstants.UNDEFINED;
mResolvedHeight = YogaConstants.UNDEFINED;
mComponentContext = null;
mResources = null;
mComponents.clear();
mNestedTree = null;
mNestedTreeHolder = null;
if (mNodeInfo != null) {
mNodeInfo.release();
mNodeInfo = null;
}
mImportantForAccessibility = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
mDuplicateParentState = false;
mBackground = null;
mForeground = null;
mForceViewWrapping = false;
mVisibleRatio = 0;
mVisibleHandler = null;
mFocusedHandler = null;
mUnfocusedHandler = null;
mFullImpressionHandler = null;
mInvisibleHandler = null;
mPrivateFlags = 0L;
mTransitionKey = null;
mBorderColor = Color.TRANSPARENT;
mIsPaddingPercent = null;
if (mTouchExpansion != null) {
ComponentsPools.release(mTouchExpansion);
mTouchExpansion = null;
}
if (mNestedTreePadding != null) {
ComponentsPools.release(mNestedTreePadding);
mNestedTreePadding = null;
}
if (mNestedTreeBorderWidth != null) {
ComponentsPools.release(mNestedTreeBorderWidth);
mNestedTreeBorderWidth = null;
}
mLastWidthSpec = DiffNode.UNSPECIFIED;
mLastHeightSpec = DiffNode.UNSPECIFIED;
mLastMeasuredHeight = DiffNode.UNSPECIFIED;
mLastMeasuredWidth = DiffNode.UNSPECIFIED;
mDiffNode = null;
mCachedMeasuresValid = false;
mIsNestedTreeHolder = false;
mTestKey = null;
if (mPendingTreeProps != null) {
mPendingTreeProps.reset();
ComponentsPools.release(mPendingTreeProps);
mPendingTreeProps = null;
}
}
private NodeInfo getOrCreateNodeInfo() {
if (mNodeInfo == null) {
mNodeInfo = NodeInfo.acquire();
}
return mNodeInfo;
}
/**
* Check that the root of the nested tree we are going to use, has valid layout directions
* with its main tree holder node.
*/
static boolean hasValidLayoutDirectionInNestedTree(
InternalNode nestedTreeHolder,
InternalNode nestedTree) {
final boolean nestedTreeHasExplicitDirection =
((nestedTree.mPrivateFlags & PFLAG_LAYOUT_DIRECTION_IS_SET) != 0L);
final boolean hasSameLayoutDirection =
(nestedTree.getResolvedLayoutDirection() == nestedTreeHolder.getResolvedLayoutDirection());
return nestedTreeHasExplicitDirection || hasSameLayoutDirection;
}
/**
* Adds an item to a possibly nulled list to defer the allocation as long as possible.
*/
private static <A> List<A> addOrCreateList(@Nullable List<A> list, A item) {
if (list == null) {
list = new LinkedList<>();
}
list.add(item);
return list;
}
private void setIsPaddingPercent(YogaEdge edge, boolean isPaddingPercent) {
if (mIsPaddingPercent == null && isPaddingPercent) {
mIsPaddingPercent = new boolean[YogaEdge.ALL.intValue() + 1];
}
if (mIsPaddingPercent != null) {
mIsPaddingPercent[edge.intValue()] = isPaddingPercent;
}
}
private boolean isPaddingPercent(YogaEdge edge) {
return (mIsPaddingPercent == null) ? false : mIsPaddingPercent[edge.intValue()];
}
/**
* Crash if the given node has context specific style set.
*/
static void assertContextSpecificStyleNotSet(InternalNode node) {
List<CharSequence> errorTypes = null;
if ((node.mPrivateFlags & PFLAG_ALIGN_SELF_IS_SET) != 0L) {
errorTypes = addOrCreateList(errorTypes, "alignSelf");
}
if ((node.mPrivateFlags & PFLAG_POSITION_TYPE_IS_SET) != 0L) {
errorTypes = addOrCreateList(errorTypes, "positionType");
}
if ((node.mPrivateFlags & PFLAG_FLEX_IS_SET) != 0L) {
errorTypes = addOrCreateList(errorTypes, "flex");
}
if ((node.mPrivateFlags & PFLAG_FLEX_GROW_IS_SET) != 0L) {
errorTypes = addOrCreateList(errorTypes, "flexGrow");
}
if ((node.mPrivateFlags & PFLAG_MARGIN_IS_SET) != 0L) {
errorTypes = addOrCreateList(errorTypes, "margin");
}
if (errorTypes != null) {
final CharSequence errorStr = TextUtils.join(", ", errorTypes);
throw new IllegalStateException("You should not set " + errorStr + " to a root layout in "
+ node.getRootComponent().getLifecycle());
}
}
public TreeProps getPendingTreeProps() {
return mPendingTreeProps;
}
private <T extends Drawable> void setPaddingFromDrawableReference(Reference<T> ref) {
if (ref == null) {
return;
}
final T drawable = Reference.acquire(mComponentContext,ref);
if (drawable != null) {
final Rect backgroundPadding = ComponentsPools.acquireRect();
if (drawable.getPadding(backgroundPadding)) {
paddingPx(LEFT, backgroundPadding.left);
paddingPx(TOP, backgroundPadding.top);
paddingPx(RIGHT, backgroundPadding.right);
paddingPx(BOTTOM, backgroundPadding.bottom);
}
Reference.release(mComponentContext, drawable, ref);
ComponentsPools.release(backgroundPadding);
}
}
}