2014-10-29 08:01:22 -07:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2014, 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.
|
|
|
|
*/
|
2014-12-05 14:11:14 +00:00
|
|
|
|
2014-09-18 15:15:21 -07:00
|
|
|
package com.facebook.csslayout;
|
|
|
|
|
2014-12-05 14:11:14 +00:00
|
|
|
import javax.annotation.Nullable;
|
|
|
|
|
2014-09-18 15:15:21 -07:00
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A CSS Node. It has a style object you can manipulate at {@link #style}. After calling
|
|
|
|
* {@link #calculateLayout()}, {@link #layout} will be filled with the results of the layout.
|
|
|
|
*/
|
|
|
|
public class CSSNode {
|
|
|
|
|
|
|
|
private static enum LayoutState {
|
|
|
|
/**
|
|
|
|
* Some property of this node or its children has changes and the current values in
|
|
|
|
* {@link #layout} are not valid.
|
|
|
|
*/
|
|
|
|
DIRTY,
|
|
|
|
|
|
|
|
/**
|
2014-10-08 15:42:51 -07:00
|
|
|
* This node has a new layout relative to the last time {@link #markLayoutSeen()} was called.
|
2014-09-18 15:15:21 -07:00
|
|
|
*/
|
|
|
|
HAS_NEW_LAYOUT,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@link #layout} is valid for the node's properties and this layout has been marked as
|
2014-10-08 15:42:51 -07:00
|
|
|
* having been seen.
|
2014-09-18 15:15:21 -07:00
|
|
|
*/
|
|
|
|
UP_TO_DATE,
|
|
|
|
}
|
|
|
|
|
2014-11-20 17:28:54 +00:00
|
|
|
public static final int SPACING_ALL = 0;
|
|
|
|
public static final int SPACING_VERTICAL = 1;
|
|
|
|
public static final int SPACING_HORIZONTAL = 2;
|
|
|
|
public static final int SPACING_LEFT = 3;
|
|
|
|
public static final int SPACING_RIGHT = 4;
|
|
|
|
public static final int SPACING_TOP = 5;
|
|
|
|
public static final int SPACING_BOTTOM = 6;
|
|
|
|
|
|
|
|
private final float[] mMargin = new float[] {
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN
|
|
|
|
};
|
|
|
|
|
2014-11-20 17:59:40 +00:00
|
|
|
private final float[] mPadding = new float[] {
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN
|
|
|
|
};
|
|
|
|
|
|
|
|
private final float[] mBorder = new float[] {
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN,
|
|
|
|
Float.NaN
|
|
|
|
};
|
|
|
|
|
2014-09-18 15:15:21 -07:00
|
|
|
// Only one copy kept around to keep from allocating a bunch of MeasureOutput objects
|
|
|
|
// NOT THREAD SAFE! NOT RE-ENTRANT SAFE!
|
|
|
|
private static final MeasureOutput MEASURE_OUTPUT = new MeasureOutput();
|
|
|
|
|
|
|
|
public static interface MeasureFunction {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Should measure the given node and put the result in the given MeasureOutput.
|
|
|
|
*
|
|
|
|
* NB: measure is NOT guaranteed to be threadsafe/re-entrant safe!
|
|
|
|
*/
|
|
|
|
public void measure(CSSNode node, float width, MeasureOutput measureOutput);
|
|
|
|
}
|
|
|
|
|
|
|
|
// VisibleForTesting
|
|
|
|
/*package*/ final CSSStyle style = new CSSStyle();
|
|
|
|
/*package*/ final CSSLayout layout = new CSSLayout();
|
|
|
|
/*package*/ final CachedCSSLayout lastLayout = new CachedCSSLayout();
|
|
|
|
|
|
|
|
// 4 is kinda arbitrary, but the default of 10 seems really high for an average View.
|
|
|
|
private final ArrayList<CSSNode> mChildren = new ArrayList<CSSNode>(4);
|
|
|
|
|
|
|
|
private CSSNode mParent;
|
|
|
|
private MeasureFunction mMeasureFunction = null;
|
|
|
|
private LayoutState mLayoutState = LayoutState.DIRTY;
|
2014-12-02 18:52:57 +00:00
|
|
|
private int mTag;
|
|
|
|
|
|
|
|
public int getTag() {
|
|
|
|
return mTag;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setTag(int tag) {
|
|
|
|
mTag = tag;
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
|
|
|
|
public int getChildCount() {
|
|
|
|
return mChildren.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
public CSSNode getChildAt(int i) {
|
|
|
|
return mChildren.get(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addChildAt(CSSNode child, int i) {
|
|
|
|
if (child.mParent != null) {
|
|
|
|
throw new IllegalStateException("Child already has a parent, it must be removed first.");
|
|
|
|
}
|
|
|
|
|
|
|
|
mChildren.add(i, child);
|
|
|
|
child.mParent = this;
|
|
|
|
dirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeChildAt(int i) {
|
|
|
|
mChildren.remove(i).mParent = null;
|
|
|
|
dirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
public CSSNode getParent() {
|
|
|
|
return mParent;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMeasureFunction(MeasureFunction measureFunction) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(mMeasureFunction, measureFunction)) {
|
|
|
|
mMeasureFunction = measureFunction;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isMeasureDefined() {
|
|
|
|
return mMeasureFunction != null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*package*/ MeasureOutput measure(float width) {
|
|
|
|
if (!isMeasureDefined()) {
|
|
|
|
throw new RuntimeException("Measure function isn't defined!");
|
|
|
|
}
|
|
|
|
MEASURE_OUTPUT.height = CSSConstants.UNDEFINED;
|
|
|
|
MEASURE_OUTPUT.width = CSSConstants.UNDEFINED;
|
|
|
|
mMeasureFunction.measure(this, width, MEASURE_OUTPUT);
|
|
|
|
return MEASURE_OUTPUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs the actual layout and saves the results in {@link #layout}
|
|
|
|
*/
|
|
|
|
public void calculateLayout() {
|
2014-12-02 18:52:57 +00:00
|
|
|
layout.resetResult();
|
2014-09-18 15:15:21 -07:00
|
|
|
LayoutEngine.layoutNode(this, CSSConstants.UNDEFINED);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See {@link LayoutState#DIRTY}.
|
|
|
|
*/
|
|
|
|
/*package*/ boolean isDirty() {
|
|
|
|
return mLayoutState == LayoutState.DIRTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See {@link LayoutState#HAS_NEW_LAYOUT}.
|
|
|
|
*/
|
|
|
|
public boolean hasNewLayout() {
|
|
|
|
return mLayoutState == LayoutState.HAS_NEW_LAYOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void dirty() {
|
|
|
|
if (mLayoutState == LayoutState.DIRTY) {
|
|
|
|
return;
|
|
|
|
} else if (mLayoutState == LayoutState.HAS_NEW_LAYOUT) {
|
2014-10-08 15:42:51 -07:00
|
|
|
throw new IllegalStateException("Previous layout was ignored! markLayoutSeen() never called");
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
mLayoutState = LayoutState.DIRTY;
|
|
|
|
|
|
|
|
if (mParent != null) {
|
|
|
|
mParent.dirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*package*/ void markHasNewLayout() {
|
|
|
|
mLayoutState = LayoutState.HAS_NEW_LAYOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-10-08 15:42:51 -07:00
|
|
|
* Tells the node that the current values in {@link #layout} have been seen. Subsequent calls
|
2014-09-18 15:15:21 -07:00
|
|
|
* to {@link #hasNewLayout()} will return false until this node is laid out with new parameters.
|
|
|
|
* You must call this each time the layout is generated if the node has a new layout.
|
|
|
|
*/
|
2014-10-08 15:42:51 -07:00
|
|
|
public void markLayoutSeen() {
|
2014-09-18 15:15:21 -07:00
|
|
|
if (!hasNewLayout()) {
|
2014-10-08 15:42:51 -07:00
|
|
|
throw new IllegalStateException("Expected node to have a new layout to be seen!");
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
mLayoutState = LayoutState.UP_TO_DATE;
|
|
|
|
}
|
|
|
|
|
2014-12-02 18:52:57 +00:00
|
|
|
private void toStringWithIndentation(StringBuilder result, int level) {
|
|
|
|
// Spaces and tabs are dropped by IntelliJ logcat integration, so rely on __ instead.
|
|
|
|
StringBuilder indentation = new StringBuilder();
|
|
|
|
for (int i = 0; i < level; ++i) {
|
|
|
|
indentation.append("__");
|
|
|
|
}
|
|
|
|
|
|
|
|
result.append(indentation.toString());
|
|
|
|
result.append(layout.toString());
|
2014-09-18 15:15:21 -07:00
|
|
|
|
|
|
|
if (getChildCount() == 0) {
|
2014-12-02 18:52:57 +00:00
|
|
|
return;
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
2014-12-02 18:52:57 +00:00
|
|
|
result.append(", children: [\n");
|
2014-09-18 15:15:21 -07:00
|
|
|
for (int i = 0; i < getChildCount(); i++) {
|
2014-12-02 18:52:57 +00:00
|
|
|
getChildAt(i).toStringWithIndentation(result, level + 1);
|
|
|
|
result.append("\n");
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
2014-12-02 18:52:57 +00:00
|
|
|
result.append(indentation + "]");
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
2014-12-02 18:52:57 +00:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
this.toStringWithIndentation(sb, 0);
|
|
|
|
return sb.toString();
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
2014-10-08 15:42:51 -07:00
|
|
|
protected boolean valuesEqual(float f1, float f2) {
|
|
|
|
return FloatUtil.floatsEqual(f1, f2);
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
2014-12-05 14:11:14 +00:00
|
|
|
protected <T> boolean valuesEqual(@Nullable T o1, @Nullable T o2) {
|
2014-09-18 15:15:21 -07:00
|
|
|
if (o1 == null) {
|
|
|
|
return o2 == null;
|
|
|
|
}
|
|
|
|
return o1.equals(o2);
|
|
|
|
}
|
|
|
|
|
2014-10-08 15:42:51 -07:00
|
|
|
public void setFlexDirection(CSSFlexDirection flexDirection) {
|
|
|
|
if (!valuesEqual(style.flexDirection, flexDirection)) {
|
|
|
|
style.flexDirection = flexDirection;
|
2014-09-18 15:15:21 -07:00
|
|
|
dirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setJustifyContent(CSSJustify justifyContent) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.justifyContent, justifyContent)) {
|
|
|
|
style.justifyContent = justifyContent;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setAlignItems(CSSAlign alignItems) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.alignItems, alignItems)) {
|
|
|
|
style.alignItems = alignItems;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setAlignSelf(CSSAlign alignSelf) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.alignSelf, alignSelf)) {
|
|
|
|
style.alignSelf = alignSelf;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionType(CSSPositionType positionType) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.positionType, positionType)) {
|
|
|
|
style.positionType = positionType;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
2014-12-12 12:03:31 +00:00
|
|
|
public void setWrap(CSSWrap flexWrap) {
|
|
|
|
if (!valuesEqual(style.flexWrap, flexWrap)) {
|
|
|
|
style.flexWrap = flexWrap;
|
|
|
|
dirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-18 15:15:21 -07:00
|
|
|
public void setFlex(float flex) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.flex, flex)) {
|
|
|
|
style.flex = flex;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
2014-11-20 17:28:54 +00:00
|
|
|
public void setMargin(int spacingType, float margin) {
|
|
|
|
setSpacing(mMargin, spacingType, margin, style.margin);
|
|
|
|
}
|
|
|
|
|
2014-11-20 17:59:40 +00:00
|
|
|
public void setPadding(int spacingType, float padding) {
|
|
|
|
setSpacing(mPadding, spacingType, padding, style.padding);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setBorder(int spacingType, float border) {
|
|
|
|
setSpacing(mBorder, spacingType, border, style.border);
|
|
|
|
}
|
|
|
|
|
2014-11-20 17:28:54 +00:00
|
|
|
protected void setSpacing(float[] spacingDef, int spacingType, float spacing, float[] cssStyle) {
|
|
|
|
if (!valuesEqual(spacingDef[spacingType], spacing)) {
|
|
|
|
spacingDef[spacingType] = spacing;
|
|
|
|
cssStyle[CSSStyle.SPACING_TOP] =
|
|
|
|
!Float.isNaN(spacingDef[SPACING_TOP]) ? spacingDef[SPACING_TOP]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_VERTICAL]) ? spacingDef[SPACING_VERTICAL]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_ALL]) ? spacingDef[SPACING_ALL]
|
|
|
|
: 0;
|
|
|
|
cssStyle[CSSStyle.SPACING_BOTTOM] =
|
|
|
|
!Float.isNaN(spacingDef[SPACING_BOTTOM]) ? spacingDef[SPACING_BOTTOM]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_VERTICAL]) ? spacingDef[SPACING_VERTICAL]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_ALL]) ? spacingDef[SPACING_ALL]
|
|
|
|
: 0;
|
|
|
|
cssStyle[CSSStyle.SPACING_LEFT] =
|
|
|
|
!Float.isNaN(spacingDef[SPACING_LEFT]) ? spacingDef[SPACING_LEFT]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_HORIZONTAL]) ? spacingDef[SPACING_HORIZONTAL]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_ALL]) ? spacingDef[SPACING_ALL]
|
|
|
|
: 0;
|
|
|
|
cssStyle[CSSStyle.SPACING_RIGHT] =
|
|
|
|
!Float.isNaN(spacingDef[SPACING_RIGHT]) ? spacingDef[SPACING_RIGHT]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_HORIZONTAL]) ? spacingDef[SPACING_HORIZONTAL]
|
|
|
|
: !Float.isNaN(spacingDef[SPACING_ALL]) ? spacingDef[SPACING_ALL]
|
|
|
|
: 0;
|
2014-10-08 15:42:51 -07:00
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionTop(float positionTop) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.positionTop, positionTop)) {
|
|
|
|
style.positionTop = positionTop;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionBottom(float positionBottom) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.positionBottom, positionBottom)) {
|
|
|
|
style.positionBottom = positionBottom;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionLeft(float positionLeft) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.positionLeft, positionLeft)) {
|
|
|
|
style.positionLeft = positionLeft;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionRight(float positionRight) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.positionRight, positionRight)) {
|
|
|
|
style.positionRight = positionRight;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setStyleWidth(float width) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.width, width)) {
|
|
|
|
style.width = width;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setStyleHeight(float height) {
|
2014-10-08 15:42:51 -07:00
|
|
|
if (!valuesEqual(style.height, height)) {
|
|
|
|
style.height = height;
|
|
|
|
dirty();
|
|
|
|
}
|
2014-09-18 15:15:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public float getLayoutX() {
|
|
|
|
return layout.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getLayoutY() {
|
|
|
|
return layout.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getLayoutWidth() {
|
|
|
|
return layout.width;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getLayoutHeight() {
|
|
|
|
return layout.height;
|
|
|
|
}
|
|
|
|
}
|