mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-19 05:51:35 +08:00
082329e591
* feat(layout): support scale factor * feat(layout): add scale factor interface * feat(layout): add hpconfig file * feat(layout): add parameter for dolayout function * feat(layout): support config * feat(layout): add default config * feat(layout): add method HPNodeNewWithConfig
1560 lines
56 KiB
C++
Executable File
1560 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 <string.h>
|
|
|
|
#include <algorithm>
|
|
#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(HPConfigRef config) {
|
|
context = nullptr;
|
|
parent = nullptr;
|
|
measure = nullptr;
|
|
dirtiedFunc = nullptr;
|
|
_config = config;
|
|
|
|
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(reinterpret_cast<void*>(result.position), 0, sizeof(float) * 4);
|
|
memset(reinterpret_cast<void*>(result.cachedPosition), 0, sizeof(float) * 4);
|
|
memset(reinterpret_cast<void*>(result.margin), 0, sizeof(float) * 4);
|
|
memset(reinterpret_cast<void*>(result.padding), 0, sizeof(float) * 4);
|
|
memset(reinterpret_cast<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(ianwang): 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;
|
|
}
|
|
|
|
void HPNode::SetConfig(HPConfigRef config) {
|
|
_config = config;
|
|
}
|
|
|
|
HPConfigRef HPNode::GetConfig() {
|
|
return _config;
|
|
};
|
|
|
|
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,
|
|
HPConfigRef config,
|
|
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, config->GetScaleFactor()); // 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(ianwang): 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(ianwang): 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(ianwang): 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(ianwang): 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(ianwang): for measure, I put the calculate of flex container's cross size in
|
|
// here..
|
|
// TODO(ianwang): 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(ianwang): 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(ianwang): 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(ianwang): 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(ianwang): 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, float scaleFactor) {
|
|
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, scaleFactor, false, isTextNode);
|
|
result.position[CSSTop] = HPRoundValueToPixelGrid(top, scaleFactor, 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, scaleFactor,(isTextNode && hasFractionalWidth),
|
|
(isTextNode && !hasFractionalWidth)) -
|
|
HPRoundValueToPixelGrid(absLeft, scaleFactor, false, isTextNode);
|
|
|
|
result.dim[DimHeight] = HPRoundValueToPixelGrid(absBottom, scaleFactor, (isTextNode && hasFractionalHeight),
|
|
(isTextNode && !hasFractionalHeight)) -
|
|
HPRoundValueToPixelGrid(absTop, scaleFactor, false, isTextNode);
|
|
std::vector<HPNodeRef>& items = children;
|
|
for (size_t i = 0; i < items.size(); i++) {
|
|
HPNodeRef item = items[i];
|
|
item->convertLayoutResult(absLeft, absTop, scaleFactor);
|
|
}
|
|
}
|