blob: 0818c15558b8bea288f2215126f8828bc7f23b3e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Copyright (c) 2014, Facebook, Inc. All rights reserved. <p/> This source code is licensed under
* the BSD-cssstyle 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.taobao.weex.dom.flex;
//import javax.annotation.Nullable;
import android.support.annotation.NonNull;
import com.taobao.weex.WXEnvironment;
import com.taobao.weex.utils.WXLogUtils;
import java.util.ArrayList;
import static com.taobao.weex.dom.flex.CSSLayout.DIMENSION_HEIGHT;
import static com.taobao.weex.dom.flex.CSSLayout.DIMENSION_WIDTH;
import static com.taobao.weex.dom.flex.CSSLayout.POSITION_BOTTOM;
import static com.taobao.weex.dom.flex.CSSLayout.POSITION_LEFT;
import static com.taobao.weex.dom.flex.CSSLayout.POSITION_RIGHT;
import static com.taobao.weex.dom.flex.CSSLayout.POSITION_TOP;
//import com.facebook.infer.annotation.Assertions;
/**
* A CSS Node. It has a cssstyle object you can manipulate at {@link #cssstyle}. After calling
* {@link #calculateLayout(CSSLayoutContext)}, {@link #csslayout} will be filled with the results of
* the csslayout.
*/
public class CSSNode {
// VisibleForTesting
/*package*/public final CSSStyle cssstyle = new CSSStyle();
/*package*/ public final CSSLayout csslayout = new CSSLayout();
/*package*/ final CachedCSSLayout lastLayout = new CachedCSSLayout();
public int lineIndex = 0;
/*package*/ CSSNode nextAbsoluteChild;
/*package*/ CSSNode nextFlexChild;
private ArrayList<CSSNode> mChildren;
private CSSNode mParent;
private MeasureFunction mMeasureFunction = null;
private LayoutState mLayoutState = LayoutState.DIRTY;
private boolean mShow = true;
private boolean mIsLayoutChanged = true;
public boolean isShow() {
return mShow;
}
public void setVisible(boolean isShow) {
if (!mShow && isShow) {
mLayoutState = LayoutState.UP_TO_DATE;
}
mShow = isShow;
dirty();
}
public void markLayoutStateUpdated(){
this.mLayoutState = LayoutState.UP_TO_DATE;
}
/**
* whether layout changed when {@link #updateLastLayout(CSSLayout)} invoked last time.
* @return
*/
public boolean isLayoutChanged(){
return mIsLayoutChanged;
}
/**
* must invoke after every layout finished,even nothing changed.
* @param newLayout
* @return
*/
public boolean updateLastLayout(CSSLayout newLayout){
mIsLayoutChanged = !lastLayout.equals(newLayout);
if(mIsLayoutChanged) {
lastLayout.copy(newLayout);
}
return mIsLayoutChanged;
}
public int getChildCount() {
return mChildren == null ? 0 : mChildren.size();
}
public CSSNode getChildAt(int i) {
// Assertions.assertNotNull(mChildren);
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.");
}
if (mChildren == null) {
// 4 is kinda arbitrary, but the default of 10 seems really high for an average View.
mChildren = new ArrayList<CSSNode>(4);
}
mChildren.add(i, child);
child.mParent = this;
dirty();
}
public CSSNode removeChildAt(int i) {
// Assertions.assertNotNull(mChildren);
CSSNode removed = mChildren.remove(i);
removed.mParent = null;
dirty();
return removed;
}
public void setParentNull() {
mParent = null;
}
public CSSNode getParent() {
return mParent;
}
/**
* @return the index of the given child, or -1 if the child doesn't exist in this node.
*/
public int indexOf(CSSNode child) {
// Assertions.assertNotNull(mChildren);
return mChildren.indexOf(child);
}
public void setMeasureFunction(MeasureFunction measureFunction) {
if (mMeasureFunction != measureFunction) {
mMeasureFunction = measureFunction;
dirty();
}
}
/*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width) {
if (!isMeasureDefined()) {
throw new RuntimeException("Measure function isn't defined!");
}
measureOutput.height = CSSConstants.UNDEFINED;
measureOutput.width = CSSConstants.UNDEFINED;
if (mMeasureFunction != null) {
mMeasureFunction.measure(this, width, measureOutput);
}
// Assertions.assertNotNull(mMeasureFunction).measure(this, width, measureOutput);
return measureOutput;
}
public boolean isMeasureDefined() {
return mMeasureFunction != null;
}
/**
* Performs the actual csslayout and saves the results in {@link #csslayout}
*/
public void calculateLayout(CSSLayoutContext layoutContext) {
csslayout.resetResult();
LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, null);
}
/**
* See {@link LayoutState#DIRTY}.
*/
protected boolean isDirty() {
return mLayoutState == LayoutState.DIRTY;
}
protected void dirty() {
if (mLayoutState == LayoutState.DIRTY) {
return;
} else if (mLayoutState == LayoutState.HAS_NEW_LAYOUT) {
if(WXEnvironment.isApkDebugable()){
WXLogUtils.d("Previous csslayout was ignored! markLayoutSeen() never called");
}
markLayoutSeen();
}
mLayoutState = LayoutState.DIRTY;
if (mParent != null && !mParent.isDirty()) {
mParent.dirty();
}
}
/*package*/ void markHasNewLayout() {
mLayoutState = LayoutState.HAS_NEW_LAYOUT;
}
/**
* Tells the node that the current values in {@link #csslayout} have been seen. Subsequent calls
* to {@link #hasNewLayout()} will return false until this node is laid out with new parameters.
* You must call this each time the csslayout is generated if the node has a new csslayout.
*/
public void markLayoutSeen() {
if (!hasNewLayout()) {
throw new IllegalStateException("Expected node to have a new csslayout to be seen!");
}
mLayoutState = LayoutState.UP_TO_DATE;
}
/**
* See {@link LayoutState#HAS_NEW_LAYOUT}.
*/
public boolean hasNewLayout() {
return mLayoutState == LayoutState.HAS_NEW_LAYOUT;
}
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(csslayout.toString());
result.append(cssstyle.toString());
if (getChildCount() == 0) {
return;
}
result.append(", children: [\n");
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).toStringWithIndentation(result, level + 1);
result.append("\n");
}
result.append(indentation + "]");
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
this.toStringWithIndentation(sb, 0);
return sb.toString();
}
protected boolean valuesEqual(float f1, float f2) {
return FloatUtil.floatsEqual(f1, f2);
}
/**
* Get this node's direction, as defined in the cssstyle.
*/
public CSSDirection getStyleDirection() {
return cssstyle.direction;
}
public void setDirection(CSSDirection direction) {
if (cssstyle.direction != direction) {
cssstyle.direction = direction;
dirty();
}
}
/**
* Get this node's flex direction, as defined by cssstyle.
*/
public CSSFlexDirection getFlexDirection() {
return cssstyle.flexDirection;
}
public void setFlexDirection(CSSFlexDirection flexDirection) {
if (cssstyle.flexDirection != flexDirection) {
cssstyle.flexDirection = flexDirection;
dirty();
}
}
/**
* Get this node's justify content, as defined by cssstyle.
*/
public CSSJustify getJustifyContent() {
return cssstyle.justifyContent;
}
public void setJustifyContent(CSSJustify justifyContent) {
if (cssstyle.justifyContent != justifyContent) {
cssstyle.justifyContent = justifyContent;
dirty();
}
}
/**
* Get this node's align items, as defined by cssstyle.
*/
public CSSAlign getAlignItems() {
return cssstyle.alignItems;
}
public void setAlignItems(CSSAlign alignItems) {
if (cssstyle.alignItems != alignItems) {
cssstyle.alignItems = alignItems;
dirty();
}
}
/**
* Get this node's align items, as defined by cssstyle.
*/
public CSSAlign getAlignSelf() {
return cssstyle.alignSelf;
}
public void setAlignSelf(CSSAlign alignSelf) {
if (cssstyle.alignSelf != alignSelf) {
cssstyle.alignSelf = alignSelf;
dirty();
}
}
/**
* Get this node's position type, as defined by cssstyle.
*/
public CSSPositionType getPositionType() {
return cssstyle.positionType;
}
public void setPositionType(CSSPositionType positionType) {
if (cssstyle.positionType != positionType) {
cssstyle.positionType = positionType;
dirty();
}
}
public void setWrap(CSSWrap flexWrap) {
if (cssstyle.flexWrap != flexWrap) {
cssstyle.flexWrap = flexWrap;
dirty();
}
}
/**
* Get this node's flex, as defined by cssstyle.
*/
public float getFlex() {
return cssstyle.flex;
}
public void setFlex(float flex) {
if (!valuesEqual(cssstyle.flex, flex)) {
cssstyle.flex = flex;
dirty();
}
}
/**
* Get this node's margin, as defined by cssstyle + default margin.
*/
public @NonNull Spacing getMargin() {
return cssstyle.margin;
}
public void setMargin(int spacingType, float margin) {
if (cssstyle.margin.set(spacingType, margin)) {
dirty();
}
}
public void setMinWidth(float minWidth) {
if (!valuesEqual(cssstyle.minWidth, minWidth)) {
cssstyle.minWidth = minWidth;
dirty();
}
}
public void setMaxWidth(float maxWidth) {
if (!valuesEqual(cssstyle.maxWidth, maxWidth)) {
cssstyle.maxWidth = maxWidth;
dirty();
}
}
public void setMinHeight(float minHeight) {
if (!valuesEqual(cssstyle.minHeight, minHeight)) {
cssstyle.minHeight = minHeight;
dirty();
}
}
public void setMaxHeight(float maxHeight) {
if (!valuesEqual(cssstyle.maxHeight, maxHeight)) {
cssstyle.maxHeight = maxHeight;
dirty();
}
}
/**
* Get this node's padding, as defined by cssstyle + default padding.
*/
public @NonNull Spacing getPadding() {
return cssstyle.padding;
}
public void setPadding(int spacingType, float padding) {
if (cssstyle.padding.set(spacingType, padding)) {
dirty();
}
}
/**
* Get this node's border, as defined by cssstyle.
*/
public @NonNull Spacing getBorder() {
return cssstyle.border;
}
public void setBorder(int spacingType, float border) {
if (cssstyle.border.set(spacingType, border)) {
dirty();
}
}
/**
* Get this node's position top, as defined by cssstyle.
*/
public float getPositionTop() {
return cssstyle.position[POSITION_TOP];
}
public void setPositionTop(float positionTop) {
if (!valuesEqual(cssstyle.position[POSITION_TOP], positionTop)) {
cssstyle.position[POSITION_TOP] = positionTop;
dirty();
}
}
/**
* Get this node's position bottom, as defined by cssstyle.
*/
public float getPositionBottom() {
return cssstyle.position[POSITION_BOTTOM];
}
public void setPositionBottom(float positionBottom) {
if (!valuesEqual(cssstyle.position[POSITION_BOTTOM], positionBottom)) {
cssstyle.position[POSITION_BOTTOM] = positionBottom;
dirty();
}
}
/**
* Get this node's position left, as defined by cssstyle.
*/
public float getPositionLeft() {
return cssstyle.position[POSITION_LEFT];
}
public void setPositionLeft(float positionLeft) {
if (!valuesEqual(cssstyle.position[POSITION_LEFT], positionLeft)) {
cssstyle.position[POSITION_LEFT] = positionLeft;
dirty();
}
}
/**
* Get this node's position right, as defined by cssstyle.
*/
public float getPositionRight() {
return cssstyle.position[POSITION_RIGHT];
}
public void setPositionRight(float positionRight) {
if (!valuesEqual(cssstyle.position[POSITION_RIGHT], positionRight)) {
cssstyle.position[POSITION_RIGHT] = positionRight;
dirty();
}
}
/**
* Get this node's width, as defined in the cssstyle.
*/
public float getStyleWidth() {
return cssstyle.dimensions[DIMENSION_WIDTH];
}
public void setStyleWidth(float width) {
if (!valuesEqual(cssstyle.dimensions[DIMENSION_WIDTH], width)) {
cssstyle.dimensions[DIMENSION_WIDTH] = width;
dirty();
}
}
/**
* Get this node's height, as defined in the cssstyle.
*/
public float getStyleHeight() {
return cssstyle.dimensions[DIMENSION_HEIGHT];
}
public void setStyleHeight(float height) {
if (!valuesEqual(cssstyle.dimensions[DIMENSION_HEIGHT], height)) {
cssstyle.dimensions[DIMENSION_HEIGHT] = height;
dirty();
}
}
public float getLayoutX() {
return csslayout.position[POSITION_LEFT];
}
public void setLayoutX(float x) {
csslayout.position[POSITION_LEFT] = x;
}
public float getLayoutY() {
return csslayout.position[POSITION_TOP];
}
public void setLayoutY(float y) {
csslayout.position[POSITION_TOP] = y;
}
public float getLayoutWidth() {
return csslayout.dimensions[DIMENSION_WIDTH];
}
public void setLayoutWidth(float width) {
csslayout.dimensions[DIMENSION_WIDTH] = width;
}
public float getLayoutHeight() {
return csslayout.dimensions[DIMENSION_HEIGHT];
}
public void setLayoutHeight(float height) {
csslayout.dimensions[DIMENSION_HEIGHT] = height;
}
public CSSDirection getLayoutDirection() {
return csslayout.direction;
}
/**
* Set a default padding (left/top/right/bottom) for this node.
*/
public void setDefaultPadding(int spacingType, float padding) {
if (cssstyle.padding.setDefault(spacingType, padding)) {
dirty();
}
}
/**
* Resets this instance to its default state. This method is meant to be used when recycling
* {@link CSSNode} instances.
*/
public void reset() {
if (mParent != null || (mChildren != null && mChildren.size() > 0)) {
throw new IllegalStateException("You should not reset an attached CSSNode");
}
cssstyle.reset();
csslayout.resetResult();
lineIndex = 0;
mLayoutState = LayoutState.DIRTY;
}
private static enum LayoutState {
/**
* Some property of this node or its children has changes and the current values in {@link
* #csslayout} are not valid.
*/
DIRTY,
/**
* This node has a new csslayout relative to the last time {@link #markLayoutSeen()} was
* called.
*/
HAS_NEW_LAYOUT,
/**
* {@link #csslayout} is valid for the node's properties and this csslayout has been marked as
* having been seen.
*/
UP_TO_DATE,
}
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);
}
public float getCSSLayoutHeight() {
return csslayout.dimensions[CSSLayout.DIMENSION_HEIGHT];
}
public float getCSSLayoutWidth() {
return csslayout.dimensions[CSSLayout.DIMENSION_WIDTH];
}
public float getCSSLayoutTop() {
return csslayout.position[CSSLayout.POSITION_TOP];
}
public float getCSSLayoutBottom() {
return csslayout.position[CSSLayout.POSITION_BOTTOM];
}
public float getCSSLayoutLeft() {
return csslayout.position[CSSLayout.POSITION_LEFT];
}
public float getCSSLayoutRight() {
return csslayout.position[CSSLayout.POSITION_RIGHT];
}
}