mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-24 02:04:23 +08:00
1603 lines
56 KiB
C++
Executable File
1603 lines
56 KiB
C++
Executable File
/* Tencent is pleased to support the open source community by making Hippy available.
|
|
* Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved.
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
|
|
#include "HPNode.h"
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
#include <string>
|
|
|
|
//the layout progress refers https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
|
|
|
|
std::string getIndentString(int indent) {
|
|
std::string str;
|
|
for (int i = 0; i < indent; i++) {
|
|
str += " ";
|
|
}
|
|
return str;
|
|
}
|
|
|
|
std::string toString(float value) {
|
|
if (isUndefined(value)) {
|
|
return "NAN";
|
|
} else {
|
|
char str[10] = { 0 };
|
|
snprintf(str, 9, "%0.f", PixelRoundInt(value));
|
|
return str;
|
|
}
|
|
}
|
|
|
|
void HPNode::printNode(uint32_t indent) {
|
|
std::string indentStr = getIndentString(indent);
|
|
std::string startStr;
|
|
startStr = indentStr
|
|
+ "<div layout=\"width:%s; height:%s; left:%s; top:%s;\" style=\"%s\">\n";
|
|
HPLogd(startStr.c_str(), toString(result.dim[0]).c_str(),
|
|
toString(result.dim[1]).c_str(), toString(result.position[0]).c_str(),
|
|
toString(result.position[1]).c_str(), style.toString().c_str());
|
|
|
|
std::vector<HPNodeRef>& items = children;
|
|
for (size_t i = 0; i < items.size(); i++) {
|
|
HPNodeRef item = items[i];
|
|
item->printNode(indent + 4);
|
|
}
|
|
std::string endStr = indentStr + "</div>\n";
|
|
HPLogd(endStr.c_str());
|
|
}
|
|
|
|
HPNode::HPNode() {
|
|
context = nullptr;
|
|
parent = nullptr;
|
|
measure = nullptr;
|
|
dirtiedFunc = nullptr;
|
|
|
|
initLayoutResult();
|
|
inInitailState = true;
|
|
}
|
|
|
|
HPNode::~HPNode() {
|
|
//remove from parent
|
|
if (parent != nullptr) {
|
|
parent->removeChild(this);
|
|
parent = nullptr;
|
|
}
|
|
|
|
//set child's parent as null
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
HPNodeRef item = children[i];
|
|
if (item != nullptr) {
|
|
item->setParent(nullptr);
|
|
}
|
|
}
|
|
|
|
children.clear();
|
|
}
|
|
|
|
void HPNode::initLayoutResult() {
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
fetchCount = 0;
|
|
#endif
|
|
isFrozen = false;
|
|
isDirty = true;
|
|
_hasNewLayout = false;
|
|
result.dim[DimWidth] = 0;
|
|
result.dim[DimHeight] = 0;
|
|
|
|
memset((void *) result.position, 0, sizeof(float) * 4);
|
|
memset((void *) result.cachedPosition, 0, sizeof(float) * 4);
|
|
memset((void *) result.margin, 0, sizeof(float) * 4);
|
|
memset((void *) result.padding, 0, sizeof(float) * 4);
|
|
memset((void *) result.border, 0, sizeof(float) * 4);
|
|
|
|
result.hadOverflow = false;
|
|
result.direction = DirectionInherit;
|
|
}
|
|
|
|
bool HPNode::reset() {
|
|
if (childCount() != 0 || getParent() != nullptr)
|
|
return false;
|
|
children.clear();
|
|
children.shrink_to_fit();
|
|
initLayoutResult();
|
|
inInitailState = true;
|
|
return true;
|
|
}
|
|
|
|
void HPNode::resetLayoutRecursive(bool isDisplayNone) {
|
|
if (inInitailState && isDisplayNone) {
|
|
return;
|
|
}
|
|
initLayoutResult();
|
|
if (!isDisplayNone) { //see HPNode::removeChild
|
|
//set result as undefined.see HPNodeChildTest.cpp
|
|
//in tests/folder
|
|
result.dim[DimWidth] = VALUE_UNDEFINED;
|
|
result.dim[DimHeight] = VALUE_UNDEFINED;
|
|
} else {
|
|
inInitailState = true; //prevent resetLayoutRecursive run many times in recursive
|
|
//in DisplayNone state, set hasNewLayout as true;
|
|
//set dirty false;
|
|
setHasNewLayout(true);
|
|
setDirty(false);
|
|
}
|
|
//if just because parent's display type change,
|
|
//not to clear child layout cache, can be reused.
|
|
layoutCache.clearCache();
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
HPNodeRef item = children[i];
|
|
item->resetLayoutRecursive(isDisplayNone);
|
|
}
|
|
}
|
|
|
|
HPStyle HPNode::getStyle() {
|
|
return style;
|
|
}
|
|
|
|
void HPNode::setStyle(const HPStyle& st) {
|
|
style = st;
|
|
//TODO:: layout if needed???
|
|
}
|
|
|
|
bool HPNode::setMeasureFunc(HPMeasureFunc _measure) {
|
|
if (measure == _measure) {
|
|
return true;
|
|
}
|
|
|
|
//not leaf node , not set measure
|
|
if (childCount() > 0) {
|
|
return false;
|
|
}
|
|
|
|
measure = _measure;
|
|
style.nodeType = _measure ? NodeTypeText : NodeTypeDefault;
|
|
markAsDirty();
|
|
return true;
|
|
}
|
|
|
|
void HPNode::setParent(HPNodeRef _parent) {
|
|
parent = _parent;
|
|
}
|
|
|
|
HPNodeRef HPNode::getParent() {
|
|
return parent;
|
|
}
|
|
|
|
void HPNode::addChild(HPNodeRef item) {
|
|
if (item == nullptr) {
|
|
return;
|
|
}
|
|
item->setParent(this);
|
|
children.push_back(item);
|
|
markAsDirty();
|
|
}
|
|
|
|
bool HPNode::insertChild(HPNodeRef item, uint32_t index) {
|
|
//measure node cannot have child.
|
|
if (item == nullptr || measure != nullptr) {
|
|
return false;
|
|
}
|
|
item->setParent(this);
|
|
children.insert(children.begin() + index, item);
|
|
markAsDirty();
|
|
return true;
|
|
}
|
|
|
|
HPNodeRef HPNode::getChild(uint32_t index) {
|
|
if (index > children.size() - 1) {
|
|
return nullptr;
|
|
}
|
|
return children[index];
|
|
}
|
|
|
|
bool HPNode::removeChild(HPNodeRef child) {
|
|
std::vector<HPNodeRef>::iterator p = std::find(children.begin(),
|
|
children.end(), child);
|
|
if (p != children.end()) {
|
|
children.erase(p);
|
|
child->setParent(nullptr);
|
|
child->resetLayoutRecursive(false);
|
|
markAsDirty();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HPNode::removeChild(uint32_t index) {
|
|
if (index > children.size() - 1) {
|
|
return false;
|
|
}
|
|
HPNodeRef child = getChild(index);
|
|
if (child != nullptr) {
|
|
child->setParent(nullptr);
|
|
child->resetLayoutRecursive(false);
|
|
}
|
|
children.erase(children.begin() + index);
|
|
markAsDirty();
|
|
return true;
|
|
}
|
|
|
|
uint32_t HPNode::childCount() {
|
|
return children.size();
|
|
}
|
|
|
|
void HPNode::setDisplayType(DisplayType displayType) {
|
|
if (style.displayType == displayType)
|
|
return;
|
|
style.displayType = displayType;
|
|
isDirty = false; //force following markAsDirty did effect to its parent
|
|
markAsDirty();
|
|
}
|
|
|
|
void HPNode::markAsDirty() {
|
|
if (!isDirty) {
|
|
setDirty(true);
|
|
if (parent) {
|
|
parent->markAsDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void HPNode::setHasNewLayout(bool hasNewLayoutOrNot) {
|
|
_hasNewLayout = hasNewLayoutOrNot;
|
|
}
|
|
|
|
bool HPNode::hasNewLayout() {
|
|
return _hasNewLayout;
|
|
}
|
|
|
|
void HPNode::setDirty(bool dirtyOrNot) {
|
|
if (isDirty == dirtyOrNot) {
|
|
return;
|
|
}
|
|
isDirty = dirtyOrNot;
|
|
if (isDirty) {
|
|
//reset layout direction to initial state
|
|
//need to calculated again
|
|
setLayoutDirection(DirectionInherit);
|
|
//if is dirty, reset frozen.
|
|
isFrozen = false;
|
|
//if is dirty, layout cache muse be in clear state.
|
|
layoutCache.clearCache();
|
|
if (dirtiedFunc != nullptr) {
|
|
dirtiedFunc(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HPNode::setDirtiedFunc(HPDirtiedFunc _dirtiedFunc) {
|
|
dirtiedFunc = _dirtiedFunc;
|
|
}
|
|
|
|
void HPNode::setContext(void * _context) {
|
|
context = _context;
|
|
}
|
|
|
|
void * HPNode::getContext() {
|
|
return context;
|
|
}
|
|
|
|
bool HPNode::isLayoutDimDefined(FlexDirection axis) {
|
|
return isDefined(result.dim[axisDim[axis]]);
|
|
}
|
|
|
|
void HPNode::setLayoutDim(FlexDirection axis, float value) {
|
|
result.dim[axisDim[axis]] = value;
|
|
}
|
|
|
|
inline void HPNode::setLayoutDirection(HPDirection direction) {
|
|
result.direction = direction;
|
|
}
|
|
|
|
inline HPDirection HPNode::getLayoutDirection() {
|
|
return result.direction;
|
|
}
|
|
|
|
float HPNode::getLayoutDim(FlexDirection axis) {
|
|
if (!isLayoutDimDefined(axis)) {
|
|
return VALUE_UNDEFINED;
|
|
}
|
|
return result.dim[axisDim[axis]];
|
|
}
|
|
|
|
float HPNode::getMainAxisDim() {
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
if (!isLayoutDimDefined(mainAxis)) {
|
|
return VALUE_UNDEFINED;
|
|
}
|
|
return result.dim[axisDim[mainAxis]];
|
|
}
|
|
|
|
float HPNode::getStartBorder(FlexDirection axis) {
|
|
return style.getStartBorder(axis);
|
|
}
|
|
|
|
float HPNode::getEndBorder(FlexDirection axis) {
|
|
return style.getEndBorder(axis);
|
|
}
|
|
|
|
float HPNode::getStartPaddingAndBorder(FlexDirection axis) {
|
|
return style.getStartPadding(axis) + style.getStartBorder(axis);
|
|
}
|
|
|
|
float HPNode::getEndPaddingAndBorder(FlexDirection axis) {
|
|
return style.getEndPadding(axis) + style.getEndBorder(axis);
|
|
}
|
|
|
|
float HPNode::getPaddingAndBorder(FlexDirection axis) {
|
|
return getStartPaddingAndBorder(axis) + getEndPaddingAndBorder(axis);
|
|
}
|
|
|
|
float HPNode::getStartMargin(FlexDirection axis) {
|
|
return style.getStartMargin(axis);
|
|
}
|
|
|
|
float HPNode::getEndMargin(FlexDirection axis) {
|
|
return style.getEndMargin(axis);
|
|
}
|
|
|
|
float HPNode::getMargin(FlexDirection axis) {
|
|
return style.getStartMargin(axis) + style.getEndMargin(axis);
|
|
}
|
|
|
|
bool HPNode::isAutoStartMargin(FlexDirection axis) {
|
|
return style.isAutoStartMargin(axis);
|
|
}
|
|
|
|
bool HPNode::isAutoEndMargin(FlexDirection axis) {
|
|
return style.isAutoEndMargin(axis);
|
|
}
|
|
|
|
void HPNode::setLayoutStartMargin(FlexDirection axis, float value) {
|
|
result.margin[axisStart[axis]] = value;
|
|
}
|
|
|
|
void HPNode::setLayoutEndMargin(FlexDirection axis, float value) {
|
|
result.margin[axisEnd[axis]] = value;
|
|
}
|
|
|
|
inline float HPNode::getLayoutMargin(FlexDirection axis) {
|
|
return getLayoutStartMargin(axis) + getLayoutEndMargin(axis);
|
|
}
|
|
|
|
float HPNode::getLayoutStartMargin(FlexDirection axis) {
|
|
return
|
|
isDefined(result.margin[axisStart[axis]]) ? result.margin[axisStart[axis]] : 0;
|
|
}
|
|
|
|
float HPNode::getLayoutEndMargin(FlexDirection axis) {
|
|
return
|
|
isDefined(result.margin[axisEnd[axis]]) ? result.margin[axisEnd[axis]] : 0;
|
|
}
|
|
|
|
/* If both axisStart and axisEnd are defined,
|
|
* then use axisStart. Otherwise use which is defined.
|
|
* @param axis flex direction
|
|
* @param forAxisStart
|
|
* true get relative value for axis start
|
|
* false get relative value for axis end
|
|
*/
|
|
float HPNode::resolveRelativePosition(FlexDirection axis, bool forAxisStart) {
|
|
if (style.positionType != PositionTypeRelative) {
|
|
return 0.0f;
|
|
}
|
|
|
|
if (isDefined(style.getStartPosition(axis))) {
|
|
float value = style.getStartPosition(axis);
|
|
return forAxisStart ? value : -value;
|
|
} else if (isDefined(style.getEndPosition(axis))) {
|
|
float value = style.getEndPosition(axis);
|
|
return forAxisStart ? -value : value;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
void HPNode::setLayoutStartPosition(FlexDirection axis, float value,
|
|
bool addRelativePosition) {
|
|
if (addRelativePosition && style.positionType == PositionTypeRelative) {
|
|
value += resolveRelativePosition(axis, true);
|
|
}
|
|
|
|
if (!FloatIsEqual(result.cachedPosition[axisStart[axis]], value)) {
|
|
result.cachedPosition[axisStart[axis]] = value;
|
|
setHasNewLayout(true);
|
|
}
|
|
|
|
result.position[axisStart[axis]] = value;
|
|
}
|
|
|
|
void HPNode::setLayoutEndPosition(FlexDirection axis, float value,
|
|
bool addRelativePosition) {
|
|
if (addRelativePosition && style.positionType == PositionTypeRelative) {
|
|
value += resolveRelativePosition(axis, false);
|
|
}
|
|
|
|
if (!FloatIsEqual(result.cachedPosition[axisEnd[axis]], value)) {
|
|
result.cachedPosition[axisEnd[axis]] = value;
|
|
setHasNewLayout(true);
|
|
}
|
|
|
|
result.position[axisEnd[axis]] = value;
|
|
}
|
|
|
|
float HPNode::getLayoutStartPosition(FlexDirection axis) {
|
|
return result.position[axisStart[axis]];
|
|
}
|
|
|
|
float HPNode::getLayoutEndPosition(FlexDirection axis) {
|
|
return result.position[axisEnd[axis]];
|
|
}
|
|
|
|
//calculate main axis by refer this node's flex direction
|
|
//and layout direction which resolved in resolveDirection.
|
|
FlexDirection HPNode::resolveMainAxis() {
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
HPDirection direction = getLayoutDirection();
|
|
if (direction == DirectionRTL) {
|
|
if (mainAxis == FLexDirectionRow) {
|
|
return FLexDirectionRowReverse;
|
|
} else if (mainAxis == FLexDirectionRowReverse) {
|
|
return FLexDirectionRow;
|
|
}
|
|
}
|
|
return mainAxis;
|
|
}
|
|
|
|
/* calculate cross axis by refer this node's flex properties
|
|
* and layout direction which resolved in resolveDirection.
|
|
* must use this method when cross axis alignment
|
|
* to determine cross start & cross end
|
|
*/
|
|
FlexDirection HPNode::resolveCrossAxis() {
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
FlexDirection crossAxis;
|
|
//cross axis's direction rely on flex wrap mode.
|
|
if (isRowDirection(mainAxis)) {
|
|
if (style.flexWrap == FlexWrapReverse) {
|
|
crossAxis = FLexDirectionColumnReverse;
|
|
} else {
|
|
crossAxis = FLexDirectionColumn;
|
|
}
|
|
} else {
|
|
if (style.flexWrap == FlexWrapReverse) {
|
|
crossAxis = FLexDirectionRowReverse;
|
|
} else {
|
|
crossAxis = FLexDirectionRow;
|
|
}
|
|
//in this situation crossAxis is Row Direction
|
|
//should think about node's layout direction(HPDirection)
|
|
HPDirection direction = getLayoutDirection();
|
|
if (direction == DirectionRTL) {
|
|
if (crossAxis == FLexDirectionRow) {
|
|
crossAxis = FLexDirectionRowReverse;
|
|
} else if (crossAxis == FLexDirectionRowReverse) {
|
|
crossAxis = FLexDirectionRow;
|
|
}
|
|
}
|
|
}
|
|
return crossAxis;
|
|
}
|
|
|
|
FlexAlign HPNode::getNodeAlign(HPNodeRef item) {
|
|
ASSERT(item != nullptr);
|
|
if (item->style.alignSelf == FlexAlignAuto) {
|
|
return style.alignItems;
|
|
}
|
|
return item->style.alignSelf;
|
|
}
|
|
|
|
float HPNode::boundAxis(FlexDirection axis, float value) {
|
|
float min = style.minDim[axisDim[axis]];
|
|
float max = style.maxDim[axisDim[axis]];
|
|
float boundValue = value;
|
|
if (!isUndefined(max) && max >= 0.0 && boundValue > max) {
|
|
boundValue = max;
|
|
}
|
|
if (!isUndefined(min) && min >= 0.0 && boundValue < min) {
|
|
boundValue = min;
|
|
}
|
|
return boundValue;
|
|
}
|
|
|
|
inline HPDirection HPNode::resolveDirection(HPDirection parentDirection) {
|
|
return
|
|
style.direction == DirectionInherit ?
|
|
(parentDirection > DirectionInherit ? parentDirection : DirectionLTR) :
|
|
style.direction;
|
|
}
|
|
|
|
//called after resolveDirection
|
|
void HPNode::resolveStyleValues() {
|
|
// if (!isDirty) {
|
|
// return;
|
|
// }
|
|
FlexDirection mainAxis = resolveMainAxis();
|
|
FlexDirection crossAxis = resolveCrossAxis();
|
|
//set layout margin value
|
|
//auto margins are treated as zero. may be modified during layout process
|
|
setLayoutStartMargin(mainAxis, getStartMargin(mainAxis));
|
|
setLayoutEndMargin(mainAxis, getEndMargin(mainAxis));
|
|
setLayoutStartMargin(crossAxis, getStartMargin(crossAxis));
|
|
setLayoutEndMargin(crossAxis, getEndMargin(crossAxis));
|
|
|
|
//set layout padding value
|
|
result.padding[axisStart[mainAxis]] = style.getStartPadding(mainAxis);
|
|
result.padding[axisEnd[mainAxis]] = style.getEndPadding(mainAxis);
|
|
result.padding[axisStart[crossAxis]] = style.getStartPadding(crossAxis);
|
|
result.padding[axisEnd[crossAxis]] = style.getEndPadding(crossAxis);
|
|
|
|
//set layout border value;
|
|
result.border[axisStart[mainAxis]] = style.getStartBorder(mainAxis);
|
|
result.border[axisEnd[mainAxis]] = style.getEndBorder(mainAxis);
|
|
result.border[axisStart[crossAxis]] = style.getStartBorder(crossAxis);
|
|
result.border[axisEnd[crossAxis]] = style.getEndBorder(crossAxis);
|
|
}
|
|
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
static int layoutCount = 0;
|
|
static int layoutCacheCount = 0;
|
|
static int measureCount = 0;
|
|
static int measureCacheCount = 0;
|
|
#endif
|
|
|
|
void HPNode::layout(float parentWidth, float parentHeight,
|
|
HPDirection parentDirection,
|
|
void * layoutContext) {
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
layoutCount = 0;
|
|
layoutCacheCount = 0;
|
|
measureCount = 0;
|
|
measureCacheCount = 0;
|
|
#endif
|
|
if (isUndefined(
|
|
style.flexBasis) && !isUndefined(style.dim[axisDim[style.flexDirection]])) {
|
|
style.flexBasis = style.dim[axisDim[style.flexDirection]];
|
|
}
|
|
|
|
//if container not set itself width and parent width is set,
|
|
//set container width as parentWidth subtract margin
|
|
bool styleWidthReset = false;
|
|
if (isUndefined(style.dim[DimWidth]) && isDefined(parentWidth)) {
|
|
float containerWidth = parentWidth - getMargin(FLexDirectionRow);
|
|
style.setDim(DimWidth, containerWidth > 0.0f ? containerWidth : 0.0f);
|
|
styleWidthReset = true;
|
|
}
|
|
|
|
bool styleHeightReset = false;
|
|
if (isUndefined(style.dim[DimHeight]) && isDefined(parentHeight)) {
|
|
float containerHeight = parentHeight - getMargin(FLexDirectionColumn);
|
|
style.setDim(DimHeight, containerHeight > 0.0f ? containerHeight : 0.0f);
|
|
styleHeightReset = true;
|
|
}
|
|
layoutImpl(parentWidth, parentHeight, parentDirection, LayoutActionLayout, layoutContext);
|
|
if (styleWidthReset) {
|
|
style.setDim(DimWidth, VALUE_UNDEFINED);
|
|
}
|
|
if (styleHeightReset) {
|
|
style.setDim(DimHeight, VALUE_UNDEFINED);
|
|
}
|
|
|
|
//calculate container's position
|
|
FlexDirection mainAxis = resolveMainAxis();
|
|
FlexDirection crossAxis = resolveCrossAxis();
|
|
setLayoutStartPosition(mainAxis, getStartMargin(mainAxis), true);
|
|
setLayoutEndPosition(mainAxis, getEndMargin(mainAxis), true);
|
|
setLayoutStartPosition(crossAxis, getStartMargin(crossAxis), true);
|
|
setLayoutEndPosition(crossAxis, getEndMargin(crossAxis), true);
|
|
|
|
//node 's layout is complete
|
|
//convert its and its descendants position and size to a integer value.
|
|
#ifndef ANDROID
|
|
convertLayoutResult(0.0f, 0.0f); //layout result convert has been taken in java . 3.8.2018. ianwang..
|
|
#endif
|
|
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
HPLog(LogLevelDebug,"HippyLayoutTime layout: count %d cache %d, measure: count %d cache %d",
|
|
layoutCount,layoutCacheCount,measureCount,measureCacheCount);
|
|
#endif
|
|
}
|
|
|
|
//3.Determine the flex base size and hypothetical main size of each item
|
|
void HPNode::calculateItemsFlexBasis(HPSize availableSize, void * layoutContext) {
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
std::vector<HPNodeRef>& items = children;
|
|
for (size_t i = 0; i < items.size(); i++) {
|
|
HPNodeRef item = items[i];
|
|
//for display none item, reset its and its descendants layout result.
|
|
if (item->style.displayType == DisplayTypeNone) {
|
|
item->resetLayoutRecursive();
|
|
continue;
|
|
}
|
|
//https://stackoverflow.com/questions/34352140/what-are-the-differences-between-flex-basis-and-width
|
|
//flex-basis has no effect on absolutely-positioned flex items. width and height properties
|
|
//would be necessary.
|
|
//Absolutely-positioned flex items do not participate in flex layout.
|
|
if (item->style.positionType == PositionTypeAbsolute) {
|
|
continue;
|
|
}
|
|
//3.Determine the flex base size and hypothetical main size of each item:
|
|
//3.1 If the item has a definite used flex basis, that's the flex base size.
|
|
if (isDefined(
|
|
item->style.getFlexBasis()) && isDefined(style.dim[axisDim[mainAxis]])) {
|
|
item->result.flexBaseSize = item->style.getFlexBasis();
|
|
} else if (isDefined(item->style.dim[axisDim[mainAxis]])) {
|
|
//flex-basis:auto:
|
|
//When specified on a flex item, the auto keyword retrieves the value
|
|
//of the main size property as the used flex-basis.
|
|
//If that value is itself auto, then the used value is content.
|
|
item->result.flexBaseSize = item->style.dim[axisDim[mainAxis]];
|
|
} else {
|
|
//3.2 Otherwise, size the item into the available space using its used flex basis
|
|
//in place of its main size,
|
|
float oldMainDim = item->style.getDim(mainAxis);
|
|
//item->style.flexBasis is auto value
|
|
item->style.setDim(mainAxis, item->style.flexBasis);
|
|
item->layoutImpl(
|
|
availableSize.width,
|
|
availableSize.height,
|
|
getLayoutDirection(),
|
|
isRowDirection(mainAxis) ?
|
|
LayoutActionMeasureWidth : LayoutActionMeasureHeight,
|
|
layoutContext);
|
|
item->style.setDim(mainAxis, oldMainDim);
|
|
|
|
item->result.flexBaseSize =
|
|
isDefined(item->result.dim[axisDim[mainAxis]]) ?
|
|
item->result.dim[axisDim[mainAxis]] : 0;
|
|
}
|
|
|
|
//item->result.dim[axisDim[mainAxis]] = item->boundAxis(mainAxis, item->result.flexBasis);
|
|
//The hypothetical main size is the item's flex base size clamped
|
|
//according to its min and max main size properties (and flooring the content box size at zero).
|
|
item->result.hypotheticalMainAxisSize = item->boundAxis(
|
|
mainAxis, item->result.flexBaseSize);
|
|
item->result.hypotheticalMainAxisMarginBoxSize = item->result
|
|
.hypotheticalMainAxisSize + item->getMargin(mainAxis);
|
|
}
|
|
}
|
|
|
|
bool HPNode::collectFlexLines(std::vector<FlexLine*>& flexLines,
|
|
HPSize availableSize) {
|
|
std::vector<HPNodeRef>& items = children;
|
|
bool sumHypotheticalMainSizeOverflow = false;
|
|
float availableWidth =
|
|
axisDim[style.flexDirection] == DimWidth ?
|
|
availableSize.width : availableSize.height;
|
|
if (isUndefined(availableWidth)) {
|
|
availableWidth = INFINITY;
|
|
}
|
|
|
|
FlexLine * line = nullptr;
|
|
int itemsSize = items.size();
|
|
int i = 0;
|
|
while (i < itemsSize) {
|
|
HPNodeRef item = items[i];
|
|
if (item->style.positionType == PositionTypeAbsolute
|
|
|| item->style.displayType == DisplayTypeNone) {
|
|
//see HippyTest.dirty_mark_all_children_as_dirty_when_display_changes
|
|
//when display changes.
|
|
if (i == itemsSize - 1 && line != nullptr) {
|
|
flexLines.push_back(line);
|
|
break;
|
|
}
|
|
//
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (line == nullptr) {
|
|
line = new FlexLine(this);
|
|
}
|
|
|
|
float leftSpace = availableWidth
|
|
- (line->sumHypotheticalMainSize
|
|
+ item->result.hypotheticalMainAxisMarginBoxSize);
|
|
if (leftSpace < 0) {
|
|
//may be line wrap happened
|
|
sumHypotheticalMainSizeOverflow = true;
|
|
}
|
|
|
|
if (style.flexWrap == FlexNoWrap) {
|
|
line->addItem(item);
|
|
if (i == itemsSize - 1) {
|
|
flexLines.push_back(line);
|
|
break;
|
|
}
|
|
i++;
|
|
} else {
|
|
if (leftSpace >= 0 || line->isEmpty()) {
|
|
line->addItem(item);
|
|
if (i == itemsSize - 1) {
|
|
flexLines.push_back(line);
|
|
line = nullptr;
|
|
}
|
|
i++;
|
|
} else {
|
|
|
|
flexLines.push_back(line);
|
|
line = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sumHypotheticalMainSizeOverflow;
|
|
}
|
|
|
|
void HPNode::cacheLayoutOrMeasureResult(HPSize availableSize,
|
|
HPSizeMode measureMode,
|
|
FlexLayoutAction layoutAction) {
|
|
|
|
HPSize resultSize = { result.dim[DimWidth], result.dim[DimHeight] };
|
|
layoutCache.cacheResult(availableSize, resultSize, measureMode, layoutAction);
|
|
if (layoutAction == LayoutActionLayout) {
|
|
setDirty(false);
|
|
setHasNewLayout(true);
|
|
inInitailState = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* availableWidth/availableHeight has subtract its margin and padding.
|
|
*/
|
|
void HPNode::layoutSingleNode(float availableWidth,
|
|
MeasureMode widthMeasureMode,
|
|
float availableHeight,
|
|
MeasureMode heightMeasureMode,
|
|
FlexLayoutAction layoutAction,
|
|
void * layoutContext) {
|
|
|
|
if (widthMeasureMode == MeasureModeExactly
|
|
&& heightMeasureMode == MeasureModeExactly) {
|
|
result.dim[DimWidth] = availableWidth
|
|
+ getPaddingAndBorder(FLexDirectionRow);
|
|
result.dim[DimHeight] = availableHeight
|
|
+ getPaddingAndBorder(FLexDirectionColumn);
|
|
} else {
|
|
//measure text, image etc. content node;
|
|
HPSize dim = { 0, 0 };
|
|
bool needMeasure = true;
|
|
if (style.flexGrow > 0 && style.flexShrink > 0 && parent
|
|
&& parent->childCount() == 1
|
|
&& !parent->style.isDimensionAuto(FLexDirectionRow)
|
|
&& !parent->style.isDimensionAuto(FLexDirectionColumn)) {
|
|
//don't measure single grow shrink child
|
|
//see HPMeasureTest.cpp dont_measure_single_grow_shrink_child
|
|
needMeasure = false;
|
|
}
|
|
|
|
if (!needMeasure) {
|
|
dim.width = availableWidth;
|
|
dim.height = availableHeight;
|
|
} else if (measure != nullptr && needMeasure) {
|
|
dim = measure(this, availableWidth, widthMeasureMode, availableHeight,
|
|
heightMeasureMode, layoutContext);
|
|
}
|
|
|
|
result.dim[DimWidth] = boundAxis(
|
|
FLexDirectionRow,
|
|
widthMeasureMode == MeasureModeExactly ?
|
|
(availableWidth + getPaddingAndBorder(FLexDirectionRow)) :
|
|
(dim.width + getPaddingAndBorder(FLexDirectionRow)));
|
|
|
|
result.dim[DimHeight] = boundAxis(
|
|
FLexDirectionColumn,
|
|
heightMeasureMode == MeasureModeExactly ?
|
|
(availableHeight + getPaddingAndBorder(FLexDirectionColumn)) :
|
|
(dim.height + getPaddingAndBorder(FLexDirectionColumn)));
|
|
}
|
|
|
|
HPSize availableSize = { availableWidth, availableHeight };
|
|
HPSizeMode measureMode = { widthMeasureMode, heightMeasureMode };
|
|
cacheLayoutOrMeasureResult(availableSize, measureMode, layoutAction);
|
|
}
|
|
|
|
//reference: https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
|
|
void HPNode::layoutImpl(float parentWidth, float parentHeight,
|
|
HPDirection parentDirection,
|
|
FlexLayoutAction layoutAction,
|
|
void * layoutContext) {
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
if(layoutAction == LayoutActionLayout) {
|
|
layoutCount++;
|
|
} else {
|
|
measureCount++;
|
|
}
|
|
#endif
|
|
|
|
HPDirection direction = resolveDirection(parentDirection);
|
|
if (getLayoutDirection() != direction) {
|
|
setLayoutDirection(direction);
|
|
layoutCache.clearCache();
|
|
resolveStyleValues();
|
|
}
|
|
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
bool performLayout = layoutAction == LayoutActionLayout;
|
|
if (isDefined(parentWidth)) {
|
|
parentWidth -= getMargin(FLexDirectionRow);
|
|
parentWidth = parentWidth >= 0.0f ? parentWidth : 0.0f;
|
|
}
|
|
|
|
if (isDefined(parentHeight)) {
|
|
parentHeight -= getMargin(FLexDirectionColumn);
|
|
parentHeight = parentHeight >= 0.0f ? parentHeight : 0.0f;
|
|
}
|
|
|
|
//get node dim from style
|
|
float nodeWidth =
|
|
isDefined(style.dim[DimWidth]) ?
|
|
boundAxis(FLexDirectionRow, style.dim[DimWidth]) : VALUE_UNDEFINED;
|
|
|
|
float nodeHeight =
|
|
isDefined(style.dim[DimHeight]) ?
|
|
boundAxis(FLexDirectionColumn, style.dim[DimHeight]) :
|
|
VALUE_UNDEFINED;
|
|
|
|
//layoutMeasuredWidth layoutMeasuredHeight used in
|
|
//"Determine the flex base size and hypothetical main size of each item"
|
|
if (layoutAction == LayoutActionMeasureWidth && isDefined(nodeWidth)) {
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
measureCacheCount++;
|
|
#endif
|
|
result.dim[DimWidth] = nodeWidth;
|
|
return;
|
|
} else if (layoutAction == LayoutActionMeasureHeight && isDefined(nodeHeight)) {
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
measureCacheCount++;
|
|
#endif
|
|
result.dim[DimHeight] = nodeHeight;
|
|
return;
|
|
}
|
|
|
|
//9.2.Line Length Determination
|
|
//Determine the available main and cross space for the flex items.
|
|
//For each dimension, if that dimension of the flex container's content box is a definite size,
|
|
//use that; if that dimension of the flex container is being sized under a min or max-content constraint,
|
|
//the available space in that dimension is that constraint;
|
|
//otherwise, subtract the flex container's margin, border, and padding
|
|
//from the space available to the flex container in that dimension and use that value.
|
|
//This might result in an infinite value.
|
|
|
|
//get available size for layout or measure
|
|
float availableWidth = VALUE_UNDEFINED;
|
|
if (isDefined(nodeWidth)) {
|
|
availableWidth = nodeWidth - getPaddingAndBorder(FLexDirectionRow);
|
|
} else if (isDefined(parentWidth)) {
|
|
availableWidth = parentWidth - getPaddingAndBorder(FLexDirectionRow);
|
|
}
|
|
|
|
float availableHeight = VALUE_UNDEFINED;
|
|
if (isDefined(nodeHeight)) {
|
|
availableHeight = nodeHeight - getPaddingAndBorder(FLexDirectionColumn);
|
|
} else if (isDefined(parentHeight)) {
|
|
availableHeight = parentHeight - getPaddingAndBorder(FLexDirectionColumn);
|
|
}
|
|
|
|
if (isDefined(style.maxDim[DimWidth])) {
|
|
if (FloatIsEqual(style.maxDim[DimWidth], style.minDim[DimWidth])) {
|
|
style.dim[DimWidth] = style.minDim[DimWidth];
|
|
}
|
|
float maxDimWidth = style.maxDim[DimWidth]
|
|
- getPaddingAndBorder(FLexDirectionRow);
|
|
if (maxDimWidth >= 0.0f && maxDimWidth < NanAsINF(availableWidth)) {
|
|
availableWidth = maxDimWidth;
|
|
}
|
|
}
|
|
|
|
if (isDefined(style.maxDim[DimHeight])) {
|
|
if (FloatIsEqual(style.maxDim[DimHeight], style.minDim[DimHeight])) {
|
|
style.dim[DimHeight] = style.minDim[DimHeight];
|
|
}
|
|
float maxDimHeight = style.maxDim[DimHeight]
|
|
- getPaddingAndBorder(FLexDirectionColumn);
|
|
if (maxDimHeight >= 0.0f && maxDimHeight < NanAsINF(availableHeight)) {
|
|
availableHeight = maxDimHeight;
|
|
}
|
|
}
|
|
|
|
//available size to layout...
|
|
availableWidth = availableWidth < 0.0f ? 0.0f : availableWidth;
|
|
availableHeight = availableHeight < 0.0f ? 0.0f : availableHeight;
|
|
|
|
MeasureMode widthMeasureMode = MeasureModeUndefined;
|
|
if (isDefined(style.dim[DimWidth])) {
|
|
widthMeasureMode = MeasureModeExactly;
|
|
} else if (isDefined(availableWidth)) {
|
|
if (parent && parent->style.isOverflowScroll()
|
|
&& isRowDirection(parent->style.flexDirection)) {
|
|
widthMeasureMode = MeasureModeUndefined;
|
|
availableWidth = VALUE_AUTO;
|
|
} else {
|
|
widthMeasureMode = MeasureModeAtMost;
|
|
}
|
|
}
|
|
|
|
MeasureMode heightMeasureMode = MeasureModeUndefined;
|
|
if (isDefined(style.dim[DimHeight])) {
|
|
heightMeasureMode = MeasureModeExactly;
|
|
} else if (isDefined(availableHeight)) {
|
|
if (parent && parent->style.isOverflowScroll()
|
|
&& isColumnDirection(parent->style.flexDirection)) {
|
|
heightMeasureMode = MeasureModeUndefined;
|
|
availableHeight = VALUE_AUTO;
|
|
} else {
|
|
heightMeasureMode = MeasureModeAtMost;
|
|
}
|
|
}
|
|
|
|
HPSize availableSize = { availableWidth, availableHeight };
|
|
HPSizeMode measureMode = { widthMeasureMode, heightMeasureMode };
|
|
MeasureResult* cacheResult = layoutCache.getCachedMeasureResult(
|
|
availableSize, measureMode, layoutAction, measure != nullptr);
|
|
if (cacheResult != nullptr) {
|
|
//set Result....
|
|
switch (layoutAction) {
|
|
case LayoutActionMeasureWidth:
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
measureCacheCount++;
|
|
#endif
|
|
ASSERT(isDefined(cacheResult->resultSize.width));
|
|
result.dim[DimWidth] = cacheResult->resultSize.width;
|
|
break;
|
|
case LayoutActionMeasureHeight:
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
measureCacheCount++;
|
|
#endif
|
|
ASSERT(isDefined(cacheResult->resultSize.height));
|
|
result.dim[DimHeight] = cacheResult->resultSize.height;
|
|
break;
|
|
case LayoutActionLayout:
|
|
|
|
//if it's a measure node and cache result cached by LayoutActionMeasureWidth or LayoutActionMeasureHeight
|
|
//so this is first layout for current Measure Node, set hasNewLayout as true,
|
|
//if not, this node's layout result value has been fetched to java and set hasNewLayout false in FlexNode.cc
|
|
//so not set hasNewLayout as true to avoid JNI call again in FLexNode.cc
|
|
if (cacheResult->layoutAction
|
|
!= LayoutActionLayout&& measure != nullptr) {
|
|
//need assign result size if layoutAction is different 3.14.2018
|
|
result.dim[DimWidth] = cacheResult->resultSize.width;
|
|
result.dim[DimHeight] = cacheResult->resultSize.height;
|
|
cacheLayoutOrMeasureResult(availableSize, measureMode, layoutAction);
|
|
} else {
|
|
#ifdef LAYOUT_TIME_ANALYZE
|
|
layoutCacheCount++;
|
|
#endif
|
|
//do nothing..
|
|
//layoutCache.cachedLayout object is last layout result.
|
|
//used to determine need layout or not.
|
|
}
|
|
|
|
//if it's a measure node , layout could be cache by LayoutActionMeasureWidth
|
|
//or LayoutActionMeasureHeight,so in this case, we need set dirty as false;
|
|
setDirty(false);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
//before layout set result's hadOverflow as false.
|
|
if (layoutAction == LayoutActionLayout) {
|
|
result.hadOverflow = false;
|
|
}
|
|
//single element measure width and height
|
|
if ((children.size() == 0)) {
|
|
layoutSingleNode(availableWidth, widthMeasureMode, availableHeight,
|
|
heightMeasureMode, layoutAction, layoutContext);
|
|
return;
|
|
}
|
|
//3.Determine the flex base size and hypothetical main size of each item
|
|
calculateItemsFlexBasis(availableSize, layoutContext);
|
|
//9.3. Main Size Determination
|
|
// 5. Collect flex items into flex lines:
|
|
std::vector<FlexLine *> flexLines;
|
|
bool sumHypotheticalMainSizeOverflow = collectFlexLines(flexLines,
|
|
availableSize);
|
|
|
|
//get max line's main size
|
|
float maxSumItemsMainSize = 0;
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
if (flexLines[i]->sumHypotheticalMainSize > maxSumItemsMainSize) {
|
|
maxSumItemsMainSize = flexLines[i]->sumHypotheticalMainSize;
|
|
}
|
|
}
|
|
|
|
// 4. Determine the main size of the flex container using the rules of the formatting context
|
|
//in which it participates. For this computation, auto margins on flex items are treated as 0.
|
|
//TODO:: if has set , what to do for next run in determineCrossAxisSize's layoutImpl
|
|
float containerInnerMainSize = 0.0f;
|
|
if (isDefined(style.dim[axisDim[mainAxis]])) {
|
|
//MeasureModeExactly
|
|
containerInnerMainSize = style.dim[axisDim[mainAxis]]
|
|
- getPaddingAndBorder(mainAxis);
|
|
} else {
|
|
if (sumHypotheticalMainSizeOverflow) { //MeasureModeAtMost
|
|
//if sum of hypothetical MainSize > available size;
|
|
float mainInnerSize =
|
|
axisDim[mainAxis] == DimWidth ?
|
|
availableSize.width : availableSize.height;
|
|
|
|
if (maxSumItemsMainSize > mainInnerSize && !style.isOverflowScroll()) {
|
|
if (parent && parent->getNodeAlign(this) == FlexAlignStretch
|
|
&& axisDim[mainAxis] == axisDim[parent->resolveCrossAxis()]
|
|
&& style.positionType != PositionTypeAbsolute) {
|
|
//it this node has text child and node main axis(width) is stretch
|
|
//,cross axis length(height) is undefined
|
|
//text can has multi-line, text's height can affect parent's height
|
|
//in this situation, use availableSize if possible
|
|
containerInnerMainSize = mainInnerSize;
|
|
} else {
|
|
containerInnerMainSize = maxSumItemsMainSize;
|
|
}
|
|
} else {
|
|
containerInnerMainSize = mainInnerSize;
|
|
}
|
|
} else {
|
|
containerInnerMainSize = maxSumItemsMainSize;
|
|
}
|
|
}
|
|
result.dim[axisDim[mainAxis]] = boundAxis(
|
|
mainAxis, containerInnerMainSize + getPaddingAndBorder(mainAxis));
|
|
//return if its just in measure
|
|
if ((layoutAction == LayoutActionMeasureWidth && isRowDirection(mainAxis))
|
|
|| (layoutAction == LayoutActionMeasureHeight
|
|
&& isColumnDirection(mainAxis))) {
|
|
//cache layout result & state...
|
|
cacheLayoutOrMeasureResult(availableSize, measureMode, layoutAction);
|
|
//free flexLines, allocate in collectFlexLines.
|
|
//TODO:: opt.
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
delete flexLines[i];
|
|
}
|
|
return;
|
|
}
|
|
|
|
//6. Resolving Flexible Lengths
|
|
//To resolve the flexible lengths of the items within a flex line:
|
|
//TODO:://this's the only place that confirm child items main axis size, see item->setLayoutDim
|
|
determineItemsMainAxisSize(flexLines, layoutAction);
|
|
|
|
//9.4. Cross Size Determination
|
|
//calculate line's cross size in flexLines
|
|
//TODO:: The real place that Determine
|
|
//the flex container's used cross size is at step 15.
|
|
|
|
float sumLinesCrossSize = determineCrossAxisSize(flexLines, availableSize,
|
|
layoutAction, layoutContext);
|
|
|
|
if (!performLayout) {
|
|
//TODO::for measure, I put the calculate of flex container's cross size in here..
|
|
//TODO::why must in step 15 in W3 flex layout algorithm
|
|
//noted by ianwang 12.30.2017.
|
|
|
|
//15.Determine the flex container's used cross size:
|
|
//If the cross size property is a definite size, use that,
|
|
//clamped by the min and max cross size properties of the flex container.
|
|
//Otherwise, use the sum of the flex lines' cross sizes,
|
|
//clamped by the min and max cross size properties of the flex container.
|
|
FlexDirection crossAxis = resolveCrossAxis();
|
|
float crossDimSize;
|
|
if (isDefined(style.dim[axisDim[crossAxis]])) {
|
|
crossDimSize = style.dim[axisDim[crossAxis]];
|
|
} else {
|
|
crossDimSize = (sumLinesCrossSize + getPaddingAndBorder(crossAxis));
|
|
}
|
|
result.dim[axisDim[crossAxis]] = boundAxis(crossAxis, crossDimSize);
|
|
//cache layout result & state...
|
|
cacheLayoutOrMeasureResult(availableSize, measureMode, layoutAction);
|
|
|
|
//free flexLines, allocate in collectFlexLines.
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
delete flexLines[i];
|
|
}
|
|
return;
|
|
}
|
|
|
|
//9.5. Main-Axis Alignment
|
|
mainAxisAlignment(flexLines);
|
|
|
|
//9.6. Cross-Axis Alignment
|
|
//if contianer's innerCross size not defined,
|
|
//then it will be determined in step 15 of crossAxisAlignment
|
|
crossAxisAlignment(flexLines);
|
|
|
|
//free flexLines, allocate in collectFlexLines.
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
delete flexLines[i];
|
|
}
|
|
|
|
//cache layout result & state...
|
|
cacheLayoutOrMeasureResult(availableSize, measureMode, layoutAction);
|
|
//layout fixed elements...
|
|
layoutFixedItems(measureMode, layoutContext);
|
|
|
|
return;
|
|
}
|
|
|
|
//9.4. Cross Size Determination
|
|
float HPNode::determineCrossAxisSize(std::vector<FlexLine*>& flexLines,
|
|
HPSize availableSize,
|
|
FlexLayoutAction layoutAction,
|
|
void * layoutContext) {
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
FlexDirection crossAxis = resolveCrossAxis();
|
|
float sumLinesCrossSize = 0;
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
FlexLine * line = flexLines[i];
|
|
float maxItemCrossSize = 0;
|
|
for (size_t j = 0; j < line->items.size(); j++) {
|
|
HPNodeRef item = line->items[j];
|
|
//item's main axis size has been determined.
|
|
//try to calculate the hypothetical cross size of each item
|
|
//that would be stored in result.dim[crossAxis]
|
|
//align stretch may be modify this value in the later step.
|
|
|
|
//WARNING TODO::this is the only place that the Recursive flex layout happen.
|
|
//7.Determine the hypothetical cross size of each item by performing layout with
|
|
//the used main size and the available space, treating auto as fit-content.
|
|
FlexLayoutAction oldLayoutAction = layoutAction;
|
|
if (getNodeAlign(item) == FlexAlignStretch
|
|
&& item->style.isDimensionAuto(crossAxis)
|
|
&& !item->style.hasAutoMargin(crossAxis)
|
|
&& layoutAction == LayoutActionLayout) {
|
|
//Delay layout for stretch item, do layout later in step 11.
|
|
layoutAction =
|
|
axisDim[crossAxis] == DimWidth ?
|
|
LayoutActionMeasureWidth : LayoutActionMeasureHeight;
|
|
}
|
|
float oldMainDim = item->style.getDim(mainAxis);
|
|
item->style.setDim(mainAxis, item->getLayoutDim(mainAxis));
|
|
item->layoutImpl(availableSize.width, availableSize.height,
|
|
getLayoutDirection(), layoutAction, layoutContext);
|
|
item->style.setDim(mainAxis, oldMainDim);
|
|
layoutAction = oldLayoutAction;
|
|
//if child item had overflow , then transfer this state to its parent.
|
|
//see HippyTest_HadOverflowTests.spacing_overflow_in_nested_nodes in ./tests/HPHadOverflowTest.cpp
|
|
result.hadOverflow = result.hadOverflow | item->result.hadOverflow;
|
|
|
|
//TODO:: if need support baseline add here
|
|
//8.Calculate the cross size of each flex line.
|
|
//1)Collect all the flex items whose inline-axis is parallel to the main-axis,
|
|
//whose align-self is baseline, and whose cross-axis margins are both non-auto.
|
|
//Find the largest of the distances between each item's baseline and its hypothetical
|
|
//outer cross-start edge, and the largest of the distances between each item's baseline
|
|
//and its hypothetical outer cross-end edge, and sum these two values.
|
|
//2)Among all the items not collected by the previous step, find the largest outer
|
|
//hypothetical cross size.
|
|
//3)The used cross-size of the flex line is the largest of the numbers found
|
|
//in the previous two steps and zero.
|
|
|
|
//Max item cross size
|
|
float itemOutCrossSize = item->getLayoutDim(crossAxis)
|
|
+ item->getMargin(crossAxis);
|
|
if (itemOutCrossSize > maxItemCrossSize) {
|
|
maxItemCrossSize = itemOutCrossSize;
|
|
}
|
|
}
|
|
|
|
//8.Calculate the cross size of each flex line.
|
|
//clip current container cross axis size..
|
|
maxItemCrossSize = boundAxis(crossAxis, maxItemCrossSize);
|
|
line->lineCrossSize = maxItemCrossSize;
|
|
sumLinesCrossSize += maxItemCrossSize;
|
|
|
|
//single line , set line height as container inner height
|
|
if (flexLines.size() == 1 && isDefined(style.dim[axisDim[crossAxis]])) {
|
|
//if following assert is true, means front-end's style is in unsuitable state ..
|
|
//such as main axis is undefined but set flex-wrap as FlexWrap.
|
|
//ASSERT(style.flexWrap == FlexNoWrap);
|
|
float innerCrossSize = boundAxis(crossAxis, style.dim[axisDim[crossAxis]])
|
|
- getPaddingAndBorder(crossAxis);
|
|
|
|
line->lineCrossSize = innerCrossSize;
|
|
sumLinesCrossSize = innerCrossSize;
|
|
}
|
|
}
|
|
|
|
//9.Handle 'align-content: stretch' for lines
|
|
if (isDefined(style.dim[axisDim[crossAxis]])
|
|
&& style.alignContent == FlexAlignStretch) {
|
|
|
|
float innerCrossSize = boundAxis(crossAxis, style.dim[axisDim[crossAxis]])
|
|
- getPaddingAndBorder(crossAxis);
|
|
if (sumLinesCrossSize < innerCrossSize) {
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
FlexLine * line = flexLines[i];
|
|
line->lineCrossSize += (innerCrossSize - sumLinesCrossSize)
|
|
/ flexLines.size();
|
|
}
|
|
}
|
|
}
|
|
|
|
//11.Determine the used cross size of each flex item
|
|
//Think about item align-self: stretch
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
FlexLine * line = flexLines[i];
|
|
for (size_t j = 0; j < line->items.size(); j++) {
|
|
HPNodeRef item = line->items[j];
|
|
|
|
//1): If a flex item has align-self: stretch, its computed cross size property is auto,
|
|
// and neither of its cross-axis margins are auto, the used outer cross size is the used
|
|
// cross size of its flex line, clamped according to the item's
|
|
// min and max cross size properties.
|
|
//2):Otherwise,the used cross size is the item's hypothetical cross size.
|
|
if (getNodeAlign(item) == FlexAlignStretch
|
|
&& item->style.isDimensionAuto(crossAxis)
|
|
&& !item->style.hasAutoMargin(crossAxis)) {
|
|
|
|
item->result.dim[axisDim[crossAxis]] = item->boundAxis(
|
|
crossAxis, line->lineCrossSize - item->getMargin(crossAxis));
|
|
//If the flex item has align-self: stretch, redo layout for its contents,
|
|
//treating this used size as its definite cross size
|
|
//so that percentage-sized children can be resolved.
|
|
float oldMainDim = item->style.getDim(mainAxis);
|
|
float oldCrossDim = item->style.getDim(crossAxis);
|
|
item->style.setDim(mainAxis, item->getLayoutDim(mainAxis));
|
|
item->style.setDim(crossAxis, item->getLayoutDim(crossAxis));
|
|
item->layoutImpl(availableSize.width, availableSize.height,
|
|
getLayoutDirection(), layoutAction, layoutContext);
|
|
item->style.setDim(mainAxis, oldMainDim);
|
|
item->style.setDim(crossAxis, oldCrossDim);
|
|
|
|
} else {
|
|
//Otherwise, the used cross size is the item's hypothetical cross size.
|
|
//see the step7.
|
|
//item's hypothetical cross size. has been set in result.dim[axisDim[crossAxis]].
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO::Why Determine the flex container's used cross size in step 15.
|
|
return sumLinesCrossSize;
|
|
}
|
|
|
|
//See 9.7 Resolving Flexible Lengths.
|
|
void HPNode::determineItemsMainAxisSize(std::vector<FlexLine *> & flexLines,
|
|
FlexLayoutAction layoutAction) {
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
float mainAxisContentSize = result.dim[axisDim[mainAxis]]
|
|
- getPaddingAndBorder(mainAxis);
|
|
// 6. Resolve the flexible lengths of all the flex items to find their used main size (see section 9.7.)
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
FlexLine * line = flexLines[i];
|
|
line->SetContainerMainInnerSize(mainAxisContentSize);
|
|
line->FreezeInflexibleItems(layoutAction);
|
|
while (!line->ResolveFlexibleLengths()) {
|
|
ASSERT(line->totalFlexGrow >= 0);
|
|
ASSERT(line->totalFlexGrow >= 0);
|
|
}
|
|
|
|
if (layoutAction == LayoutActionLayout && line->remainingFreeSpace < 0) {
|
|
result.hadOverflow = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//9.5 Main-Axis Alignment
|
|
void HPNode::mainAxisAlignment(std::vector<FlexLine *> & flexLines) {
|
|
//TODO:: RTL::
|
|
// 12. Distribute any remaining free space. For each flex line:
|
|
FlexDirection mainAxis = style.flexDirection;
|
|
float mainAxisContentSize = getLayoutDim(mainAxis)
|
|
- getPaddingAndBorder(mainAxis);
|
|
for (size_t i = 0; i < flexLines.size(); i++) {
|
|
FlexLine * line = flexLines[i];
|
|
line->SetContainerMainInnerSize(mainAxisContentSize);
|
|
line->alignItems();
|
|
}
|
|
}
|
|
|
|
//9.6 Cross-Axis Alignment
|
|
void HPNode::crossAxisAlignment(std::vector<FlexLine *> & flexLines) {
|
|
FlexDirection crossAxis = resolveCrossAxis();
|
|
float sumLinesCrossSize = 0;
|
|
int linesCount = flexLines.size();
|
|
for (int i = 0; i < linesCount; i++) {
|
|
FlexLine * line = flexLines[i];
|
|
sumLinesCrossSize += line->lineCrossSize;
|
|
for (size_t j = 0; j < line->items.size(); j++) {
|
|
HPNodeRef item = line->items[j];
|
|
//13.Resolve cross-axis auto margins. If a flex item has auto cross-axis margins:
|
|
float remainingFreeSpace = line->lineCrossSize
|
|
- item->result.dim[axisDim[crossAxis]] - item->getMargin(crossAxis);
|
|
if (remainingFreeSpace > 0) {
|
|
//If its outer cross size (treating those auto margins as zero) is less than
|
|
//the cross size of its flex line, distribute the difference
|
|
//in those sizes equally to the auto margins.
|
|
if (item->isAutoStartMargin(crossAxis)
|
|
&& item->isAutoEndMargin(crossAxis)) {
|
|
item->setLayoutStartMargin(crossAxis, remainingFreeSpace / 2);
|
|
item->setLayoutEndMargin(crossAxis, remainingFreeSpace / 2);
|
|
} else if (item->isAutoStartMargin(crossAxis)) {
|
|
item->setLayoutStartMargin(crossAxis, remainingFreeSpace);
|
|
} else if (item->isAutoEndMargin(crossAxis)) {
|
|
item->setLayoutEndMargin(crossAxis, remainingFreeSpace);
|
|
} else {
|
|
//For margin:: assign style value to result value at this place..
|
|
item->setLayoutStartMargin(crossAxis,
|
|
item->getStartMargin(crossAxis));
|
|
item->setLayoutEndMargin(crossAxis, item->getEndMargin(crossAxis));
|
|
}
|
|
} else {
|
|
//Otherwise, if the block-start or inline-start margin
|
|
//(whichever is in the cross axis) is auto, set it to zero.
|
|
//Set the opposite margin so that the outer cross size of the
|
|
//item equals the cross size of its flex line.
|
|
item->setLayoutStartMargin(crossAxis, item->getStartMargin(crossAxis));
|
|
item->setLayoutEndMargin(crossAxis, item->getEndMargin(crossAxis));
|
|
}
|
|
|
|
//14.Align all flex items along the cross-axis per align-self,
|
|
//if neither of the item's cross-axis margins are auto.
|
|
//calculate item's offset in its line by style align-self
|
|
remainingFreeSpace = line->lineCrossSize
|
|
- item->result.dim[axisDim[crossAxis]]
|
|
- item->getLayoutMargin(crossAxis);
|
|
float offset = item->getLayoutStartMargin(crossAxis);
|
|
switch (getNodeAlign(item)) { //when align self is auto , it overwrite by align items
|
|
case FlexAlignStart:
|
|
break;
|
|
case FlexAlignCenter:
|
|
offset += remainingFreeSpace / 2;
|
|
break;
|
|
case FlexAlignEnd:
|
|
offset += remainingFreeSpace;
|
|
break;
|
|
//TODO:: case baseline alignment
|
|
default:
|
|
break;
|
|
}
|
|
//include (axisStart[crossAxis] == CSSTop) and (axisStart[crossAxis] == CSSBottom)
|
|
//For temporary store. use false parameter
|
|
item->setLayoutStartPosition(crossAxis, offset, false);
|
|
}
|
|
}
|
|
|
|
//15.Determine the flex container's used cross size:
|
|
//If the cross size property is a definite size, use that,
|
|
//clamped by the min and max cross size properties of the flex container.
|
|
//Otherwise, use the sum of the flex lines' cross sizes,
|
|
//clamped by the min and max cross size properties of the flex container.
|
|
|
|
float crossDimSize;
|
|
if (isDefined(style.dim[axisDim[crossAxis]])) {
|
|
crossDimSize = style.dim[axisDim[crossAxis]];
|
|
} else {
|
|
crossDimSize = (sumLinesCrossSize + getPaddingAndBorder(crossAxis));
|
|
}
|
|
result.dim[axisDim[crossAxis]] = boundAxis(crossAxis, crossDimSize);
|
|
|
|
//when container's cross size determined align all flex lines by align-content
|
|
//16.Align all flex lines per align-content
|
|
float innerCrossSize = result.dim[axisDim[crossAxis]]
|
|
- getPaddingAndBorder(crossAxis);
|
|
float remainingFreeSpace = innerCrossSize - sumLinesCrossSize;
|
|
float offset = getStartPaddingAndBorder(crossAxis);
|
|
float space = 0;
|
|
switch (style.alignContent) {
|
|
case FlexAlignStart:
|
|
break;
|
|
case FlexAlignCenter:
|
|
offset += remainingFreeSpace / 2;
|
|
break;
|
|
case FlexAlignEnd:
|
|
offset += remainingFreeSpace;
|
|
break;
|
|
case FlexAlignSpaceBetween:
|
|
space = remainingFreeSpace / (linesCount - 1);
|
|
break;
|
|
case FlexAlignSpaceAround:
|
|
space = remainingFreeSpace / linesCount;
|
|
offset += space / 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//flex-end::
|
|
//The cross-end margin edge of the flex item is placed flush with the cross-end edge of the line.
|
|
//crossAxisPostionStart calculated along the cross axis direction.
|
|
float crossAxisPostionStart = offset;
|
|
for (int i = 0; i < linesCount; i++) {
|
|
FlexLine * line = flexLines[i];
|
|
for (size_t j = 0; j < line->items.size(); j++) {
|
|
HPNodeRef item = line->items[j];
|
|
//include (axisStart[crossAxis] == CSSTop) and (axisStart[crossAxis] == CSSBottom)
|
|
//getLayoutStartPosition set in step 14.
|
|
item->setLayoutStartPosition(
|
|
crossAxis,
|
|
crossAxisPostionStart + item->getLayoutStartPosition(crossAxis));
|
|
//layout start position has use relative ,so end position not use it ,use false parameter.
|
|
item->setLayoutEndPosition(
|
|
crossAxis,
|
|
(getLayoutDim(crossAxis) - item->getLayoutStartPosition(crossAxis)
|
|
- item->getLayoutDim(crossAxis)),
|
|
false);
|
|
|
|
}
|
|
|
|
crossAxisPostionStart += line->lineCrossSize + space;
|
|
}
|
|
}
|
|
|
|
//4.1. Absolutely-Positioned Flex Children
|
|
//As it is out-of-flow, an absolutely-positioned child of a flex container
|
|
//does not participate in flex layout.
|
|
//The static position of an absolutely-positioned child of a flex container
|
|
//is determined such that the child is positioned as if it were the sole flex item in the flex container,
|
|
//assuming both the child and the flex container were fixed-size boxes of their used size.
|
|
//For this purpose, auto margins are treated as zero.
|
|
void HPNode::layoutFixedItems(HPSizeMode measureMode, void * layoutContext) {
|
|
FlexDirection mainAxis = resolveMainAxis();
|
|
FlexDirection crossAxis = resolveCrossAxis();
|
|
std::vector<HPNodeRef>& items = children;
|
|
for (size_t i = 0; i < items.size(); i++) {
|
|
HPNodeRef item = items[i];
|
|
//for display none item, reset its layout result.
|
|
if (item->style.displayType == DisplayTypeNone) {
|
|
item->resetLayoutRecursive();
|
|
continue;
|
|
}
|
|
if (item->style.positionType != PositionTypeAbsolute) {
|
|
continue;
|
|
}
|
|
|
|
float parentWidth = getLayoutDim(FLexDirectionRow)
|
|
- getPaddingAndBorder(FLexDirectionRow);
|
|
float parentHeight = getLayoutDim(FLexDirectionColumn)
|
|
- getPaddingAndBorder(FLexDirectionColumn);
|
|
|
|
float itemOldStyleDimMainAxis = item->style.getDim(mainAxis);
|
|
float itemOldStyleDimCrossAxis = item->style.getDim(crossAxis);
|
|
|
|
if (isUndefined(itemOldStyleDimMainAxis)
|
|
&& isDefined(item->style.getStartPosition(mainAxis))
|
|
&& isDefined(item->style.getEndPosition(mainAxis))) {
|
|
item->style.setDim(
|
|
mainAxis,
|
|
(getLayoutDim(mainAxis) - style.getStartBorder(mainAxis)
|
|
- style.getEndBorder(mainAxis)
|
|
- item->style.getStartPosition(mainAxis)
|
|
- item->style.getEndPosition(mainAxis)
|
|
- item->getMargin(mainAxis)));
|
|
}
|
|
|
|
if (isUndefined(itemOldStyleDimCrossAxis)
|
|
&& isDefined(item->style.getStartPosition(crossAxis))
|
|
&& isDefined(item->style.getEndPosition(crossAxis))) {
|
|
item->style.setDim(
|
|
crossAxis,
|
|
(getLayoutDim(crossAxis) - style.getStartBorder(crossAxis)
|
|
- style.getEndBorder(crossAxis)
|
|
- item->style.getStartPosition(crossAxis)
|
|
- item->style.getEndPosition(crossAxis)
|
|
- item->getMargin(crossAxis)));
|
|
}
|
|
|
|
|
|
item->layoutImpl(parentWidth, parentHeight, getLayoutDirection(),
|
|
LayoutActionLayout, layoutContext);
|
|
//recover item's previous style value
|
|
item->style.setDim(mainAxis, itemOldStyleDimMainAxis);
|
|
item->style.setDim(crossAxis, itemOldStyleDimCrossAxis);
|
|
//after layout, calculate fix item 's postion
|
|
//1) for main axis
|
|
calculateFixedItemPosition(item, mainAxis);
|
|
//2)for cross axis
|
|
calculateFixedItemPosition(item, crossAxis);
|
|
}
|
|
}
|
|
|
|
//when item's layout complete, update fixed item's position on Specified axis
|
|
//called in layoutFixedItems
|
|
//should be called twice, one for main axis ,one for cross axis
|
|
void HPNode::calculateFixedItemPosition(HPNodeRef item, FlexDirection axis) {
|
|
if (isDefined(item->style.getStartPosition(axis))) {
|
|
item->setLayoutStartPosition(
|
|
axis,
|
|
getStartBorder(axis) + item->getLayoutStartMargin(axis)
|
|
+ item->style.getStartPosition(axis));
|
|
item->setLayoutEndPosition(
|
|
axis,
|
|
getLayoutDim(axis) - item->getLayoutStartPosition(axis)
|
|
- item->getLayoutDim(axis));
|
|
|
|
} else if (isDefined(item->style.getEndPosition(axis))) {
|
|
item->setLayoutEndPosition(
|
|
axis,
|
|
getEndBorder(axis) + item->getLayoutEndMargin(axis)
|
|
+ item->style.getEndPosition(axis));
|
|
item->setLayoutStartPosition(
|
|
axis,
|
|
getLayoutDim(axis) - item->getLayoutEndPosition(axis)
|
|
- item->getLayoutDim(axis));
|
|
} else {
|
|
float remainingFreeSpace = getLayoutDim(axis) - getPaddingAndBorder(axis)
|
|
- item->getLayoutDim(axis);
|
|
float offset = getStartPaddingAndBorder(axis);
|
|
FlexAlign alignMode =
|
|
(axis == resolveMainAxis() ? style.justifyContent : getNodeAlign(item));
|
|
switch (alignMode) {
|
|
case FlexAlignStart:
|
|
break;
|
|
case FlexAlignCenter:
|
|
offset += remainingFreeSpace / 2;
|
|
break;
|
|
case FlexAlignEnd:
|
|
offset += remainingFreeSpace;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
item->setLayoutStartPosition(
|
|
axis,
|
|
getStartPaddingAndBorder(axis) + item->getLayoutStartMargin(axis)
|
|
+ offset);
|
|
item->setLayoutEndPosition(
|
|
axis,
|
|
getLayoutDim(axis) - item->getLayoutStartPosition(axis)
|
|
- item->getLayoutDim(axis));
|
|
}
|
|
}
|
|
|
|
|
|
//convert position and dimension values to integer value..
|
|
//absLeft, absTop is mainly think about the influence of parent's Fraction offset
|
|
//for example: if parent's Fraction offset is 0.3
|
|
//and current child offset is 0.4
|
|
//then the child's absolute offset is 0.7.
|
|
//if use roundf , roundf(0.7) == 1
|
|
//so we need absLeft, absTop parameter
|
|
void HPNode::convertLayoutResult(float absLeft, float absTop) {
|
|
if (!hasNewLayout()) {
|
|
return;
|
|
}
|
|
const float left = result.position[CSSLeft];
|
|
const float top = result.position[CSSTop];
|
|
const float width = result.dim[DimWidth];
|
|
const float height = result.dim[DimHeight];
|
|
|
|
absLeft += left;
|
|
absTop += top;
|
|
bool isTextNode = style.nodeType == NodeTypeText;
|
|
result.position[CSSLeft] = HPRoundValueToPixelGrid(left, false, isTextNode);
|
|
result.position[CSSTop] = HPRoundValueToPixelGrid(top, false, isTextNode);
|
|
|
|
const bool hasFractionalWidth = !FloatIsEqual(fmodf(width, 1.0), 0)
|
|
&& !FloatIsEqual(fmodf(width, 1.0), 1.0);
|
|
const bool hasFractionalHeight = !FloatIsEqual(fmodf(height, 1.0), 0)
|
|
&& !FloatIsEqual(fmodf(height, 1.0), 1.0);
|
|
|
|
const float absRight = absLeft + width;
|
|
const float absBottom = absTop + height;
|
|
result.dim[DimWidth] = HPRoundValueToPixelGrid(
|
|
absRight, (isTextNode && hasFractionalWidth),
|
|
(isTextNode && !hasFractionalWidth))
|
|
- HPRoundValueToPixelGrid(absLeft, false, isTextNode);
|
|
|
|
result.dim[DimHeight] = HPRoundValueToPixelGrid(
|
|
absBottom, (isTextNode && hasFractionalHeight),
|
|
(isTextNode && !hasFractionalHeight))
|
|
- HPRoundValueToPixelGrid(absTop, false, isTextNode);
|
|
std::vector<HPNodeRef>& items = children;
|
|
for (size_t i = 0; i < items.size(); i++) {
|
|
HPNodeRef item = items[i];
|
|
item->convertLayoutResult(absLeft, absTop);
|
|
}
|
|
}
|
|
|