@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
978 lines (977 loc) • 63 kB
JavaScript
import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, FlexboxLayoutBase, FlexBasisPercent, orderProperty, flexGrowProperty, flexShrinkProperty, flexWrapBeforeProperty, alignSelfProperty } from './flexbox-layout-common';
import { View } from '../../core/view';
import { layout } from '../../../utils';
export * from './flexbox-layout-common';
var EXACTLY = layout.EXACTLY;
var AT_MOST = layout.AT_MOST;
var UNSPECIFIED = layout.UNSPECIFIED;
var MEASURED_SIZE_MASK = layout.MEASURED_SIZE_MASK;
var MEASURED_STATE_TOO_SMALL = layout.MEASURED_STATE_TOO_SMALL;
function requestFlexboxLayout(value) {
const flexbox = this.parent;
if (flexbox instanceof FlexboxLayoutBase) {
flexbox.requestLayout();
}
}
View.prototype[orderProperty.setNative] = requestFlexboxLayout;
View.prototype[flexGrowProperty.setNative] = requestFlexboxLayout;
View.prototype[flexShrinkProperty.setNative] = requestFlexboxLayout;
View.prototype[flexWrapBeforeProperty.setNative] = requestFlexboxLayout;
View.prototype[alignSelfProperty.setNative] = requestFlexboxLayout;
const MATCH_PARENT = -1;
const WRAP_CONTENT = -2;
const View_sUseZeroUnspecifiedMeasureSpec = true; // NOTE: android version < M
// Long ints may not be safe in JavaScript
const MAX_SIZE = 0x00ffffff & MEASURED_SIZE_MASK;
var makeMeasureSpec = layout.makeMeasureSpec;
var getMeasureSpecMode = layout.getMeasureSpecMode;
var getMeasureSpecSize = layout.getMeasureSpecSize;
// `eachLayoutChild` iterates over children, and we need more - indexed access.
// This class tries to accomodate that by collecting all children in an
// array no more than once per measure.
class MeasureContext {
constructor(owner) {
this.owner = owner;
this.children = [];
this.owner.eachLayoutChild((child) => {
this.children.push(child);
});
}
get childrenCount() {
return this.children.length;
}
childAt(index) {
return this.children[index];
}
}
class FlexLine {
constructor() {
this._left = Number.MAX_VALUE;
this._top = Number.MAX_VALUE;
this._right = Number.MAX_VALUE;
this._bottom = Number.MAX_VALUE;
this._mainSize = 0;
this._dividerLengthInMainSize = 0;
this._crossSize = 0;
this._itemCount = 0;
this._totalFlexGrow = 0;
this._totalFlexShrink = 0;
this._maxBaseline = 0;
this._indicesAlignSelfStretch = [];
}
get left() {
return this._left;
}
get top() {
return this._top;
}
get right() {
return this._right;
}
get bottom() {
return this._bottom;
}
get mainSize() {
return this._mainSize;
}
get crossSize() {
return this._crossSize;
}
get itemCount() {
return this._itemCount;
}
get totalFlexGrow() {
return this._totalFlexGrow;
}
get totalFlexShrink() {
return this._totalFlexShrink;
}
}
class Order {
compareTo(another) {
if (this.order !== another.order) {
return this.order - another.order;
}
return this.index - another.index;
}
}
export class FlexboxLayout extends FlexboxLayoutBase {
constructor() {
// Omit divider
super(...arguments);
this._flexLines = [];
}
onMeasure(widthMeasureSpec, heightMeasureSpec) {
this.measureContext = new MeasureContext(this);
// Omit: super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (this._isOrderChangedFromLastMeasurement) {
this._reorderedIndices = this._createReorderedIndices();
}
if (!this._childrenFrozen || this._childrenFrozen.length < this.measureContext.childrenCount) {
this._childrenFrozen = new Array(this.measureContext.childrenCount);
}
switch (this.flexDirection) {
case FlexDirection.ROW:
case FlexDirection.ROW_REVERSE:
this._measureHorizontal(widthMeasureSpec, heightMeasureSpec);
break;
case FlexDirection.COLUMN:
case FlexDirection.COLUMN_REVERSE:
this._measureVertical(widthMeasureSpec, heightMeasureSpec);
break;
default:
throw new Error('Invalid value for the flex direction is set: ' + this.flexDirection);
}
this._childrenFrozen.length = 0;
}
_getReorderedChildAt(index) {
let child;
if (index < 0 || index >= this._reorderedIndices.length) {
child = null;
}
else {
const reorderedIndex = this._reorderedIndices[index];
child = this.measureContext.childAt(reorderedIndex);
}
return child;
}
_createReorderedIndices() {
const childCount = this.measureContext.childrenCount;
const orders = this._createOrders(childCount);
return this._sortOrdersIntoReorderedIndices(childCount, orders);
}
_sortOrdersIntoReorderedIndices(childCount, orders) {
orders.sort((a, b) => a.compareTo(b));
if (!this._orderCache) {
this._orderCache = [];
}
this._orderCache.length = 0;
const reorderedIndices = [];
orders.forEach((order, i) => {
reorderedIndices[i] = order.index;
this._orderCache[i] = order.order;
});
return reorderedIndices;
}
_createOrders(childCount) {
const orders = [];
for (let i = 0; i < childCount; i++) {
const child = this.measureContext.childAt(i);
const order = new Order();
order.order = FlexboxLayout.getOrder(child);
order.index = i;
orders.push(order);
}
return orders;
}
get _isOrderChangedFromLastMeasurement() {
const childCount = this.measureContext.childrenCount;
if (!this._orderCache) {
this._orderCache = [];
}
if (this._orderCache.length !== childCount) {
return true;
}
for (let i = 0; i < childCount; i++) {
const view = this.measureContext.childAt(i);
if (view === null) {
continue;
}
if (FlexboxLayout.getOrder(view) !== this._orderCache[i]) {
return true;
}
}
return false;
}
_measureHorizontal(widthMeasureSpec, heightMeasureSpec) {
const widthSize = getMeasureSpecSize(widthMeasureSpec);
const widthMode = getMeasureSpecMode(widthMeasureSpec);
const heightSize = getMeasureSpecSize(heightMeasureSpec);
const heightMode = getMeasureSpecMode(heightMeasureSpec);
let childState = 0;
this._flexLines.length = 0;
(() => {
const childCount = this.measureContext.childrenCount;
const paddingStart = FlexboxLayout.getPaddingStart(this);
const paddingEnd = FlexboxLayout.getPaddingEnd(this);
let largestHeightInRow = Number.MIN_VALUE;
let flexLine = new FlexLine();
let indexInFlexLine = 0;
flexLine._mainSize = paddingStart + paddingEnd;
for (let i = 0; i < childCount; i++) {
const child = this._getReorderedChildAt(i);
if (child === null) {
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
else if (child.isCollapsed) {
flexLine._itemCount++;
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
child._updateEffectiveLayoutValues(widthSize, widthMode, heightSize, heightMode);
const lp = child; // child.style;
if (FlexboxLayout.getAlignSelf(child) === 'stretch') {
flexLine._indicesAlignSelfStretch.push(i);
}
let childWidth = lp.effectiveWidth;
if (FlexBasisPercent.DEFAULT /*lp.flexBasisPercent*/ !== FlexBasisPercent.DEFAULT && widthMode === EXACTLY) {
childWidth = Math.round(widthSize * FlexBasisPercent.DEFAULT /*lp.flexBasisPercent*/);
}
const childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(widthMeasureSpec, lp.effectivePaddingLeft + lp.effectivePaddingRight + lp.effectiveMarginLeft + lp.effectiveMarginRight, childWidth < 0 ? WRAP_CONTENT : childWidth);
const childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(heightMeasureSpec, lp.effectivePaddingTop + lp.effectivePaddingBottom + lp.effectiveMarginTop + lp.effectiveMarginBottom, lp.effectiveHeight < 0 ? WRAP_CONTENT : lp.effectiveHeight);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
this._checkSizeConstraints(child);
childState = View.combineMeasuredStates(childState, child.getMeasuredState());
largestHeightInRow = Math.max(largestHeightInRow, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom);
if (this._isWrapRequired(child, widthMode, widthSize, flexLine._mainSize, child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight, i, indexInFlexLine)) {
if (flexLine.itemCount > 0) {
this._addFlexLine(flexLine);
}
flexLine = new FlexLine();
flexLine._itemCount = 1;
flexLine._mainSize = paddingStart + paddingEnd;
largestHeightInRow = child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom;
indexInFlexLine = 0;
}
else {
flexLine._itemCount++;
indexInFlexLine++;
}
flexLine._mainSize += child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight;
flexLine._totalFlexGrow += FlexboxLayout.getFlexGrow(child);
flexLine._totalFlexShrink += FlexboxLayout.getFlexShrink(child);
flexLine._crossSize = Math.max(flexLine._crossSize, largestHeightInRow);
// Omit divider
if (this.flexWrap !== FlexWrap.WRAP_REVERSE) {
flexLine._maxBaseline = Math.max(flexLine._maxBaseline, FlexboxLayout.getBaseline(child) + lp.effectiveMarginTop);
}
else {
flexLine._maxBaseline = Math.max(flexLine._maxBaseline, child.getMeasuredHeight() - FlexboxLayout.getBaseline(child) + lp.effectiveMarginBottom);
}
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
}
})();
this._determineMainSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec);
if (this.alignItems === AlignItems.BASELINE) {
let viewIndex = 0;
this._flexLines.forEach((flexLine) => {
let largestHeightInLine = Number.MIN_VALUE;
for (let i = viewIndex; i < viewIndex + flexLine._itemCount; i++) {
const child = this._getReorderedChildAt(i);
const lp = child; // .style;
if (this.flexWrap !== FlexWrap.WRAP_REVERSE) {
let marginTop = flexLine._maxBaseline - FlexboxLayout.getBaseline(child);
marginTop = Math.max(marginTop, lp.effectiveMarginTop);
largestHeightInLine = Math.max(largestHeightInLine, child.getActualSize().height + marginTop + lp.effectiveMarginBottom);
}
else {
let marginBottom = flexLine._maxBaseline - child.getMeasuredHeight() + FlexboxLayout.getBaseline(child);
marginBottom = Math.max(marginBottom, lp.effectiveMarginBottom);
largestHeightInLine = Math.max(largestHeightInLine, child.getActualSize().height + lp.effectiveMarginTop + marginBottom);
}
}
flexLine._crossSize = largestHeightInLine;
viewIndex += flexLine.itemCount;
});
}
this._determineCrossSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec, this.effectivePaddingTop + this.effectivePaddingBottom);
this._stretchViews(this.flexDirection, this.alignItems);
this._setMeasuredDimensionForFlex(this.flexDirection, widthMeasureSpec, heightMeasureSpec, childState);
}
_measureVertical(widthMeasureSpec, heightMeasureSpec) {
const widthSize = getMeasureSpecSize(widthMeasureSpec);
const widthMode = getMeasureSpecMode(widthMeasureSpec);
const heightSize = getMeasureSpecSize(heightMeasureSpec);
const heightMode = getMeasureSpecMode(heightMeasureSpec);
let childState = 0;
this._flexLines.length = 0;
const childCount = this.measureContext.childrenCount;
const paddingTop = this.effectivePaddingTop;
const paddingBottom = this.effectivePaddingBottom;
let largestWidthInColumn = Number.MIN_VALUE;
let flexLine = new FlexLine();
flexLine._mainSize = paddingTop + paddingBottom;
let indexInFlexLine = 0;
for (let i = 0; i < childCount; i++) {
const child = this._getReorderedChildAt(i);
if (child === null) {
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
else if (child.isCollapsed) {
flexLine._itemCount++;
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
child._updateEffectiveLayoutValues(widthSize, widthMode, heightSize, heightMode);
const lp = child; // .style;
if (FlexboxLayout.getAlignSelf(child) === 'stretch') {
flexLine._indicesAlignSelfStretch.push(i);
}
let childHeight = lp.effectiveHeight;
// TODO: This should always be false
if (FlexBasisPercent.DEFAULT /* lp.flexBasisPercent */ !== FlexBasisPercent.DEFAULT && heightMode === EXACTLY) {
childHeight = Math.round(heightSize * FlexBasisPercent.DEFAULT /* lp.flexBasisPercent */);
}
const childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(widthMeasureSpec, this.effectivePaddingLeft + this.effectivePaddingRight + lp.effectiveMarginLeft + lp.effectiveMarginRight, lp.effectiveWidth < 0 ? WRAP_CONTENT : lp.effectiveWidth);
const childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(heightMeasureSpec, this.effectivePaddingTop + this.effectivePaddingBottom + lp.effectiveMarginTop + lp.effectiveMarginBottom, childHeight < 0 ? WRAP_CONTENT : childHeight);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
this._checkSizeConstraints(child);
childState = View.combineMeasuredStates(childState, child.getMeasuredState());
largestWidthInColumn = Math.max(largestWidthInColumn, child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight);
if (this._isWrapRequired(child, heightMode, heightSize, flexLine.mainSize, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom, i, indexInFlexLine)) {
if (flexLine._itemCount > 0) {
this._addFlexLine(flexLine);
}
flexLine = new FlexLine();
flexLine._itemCount = 1;
flexLine._mainSize = paddingTop + paddingBottom;
largestWidthInColumn = child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight;
indexInFlexLine = 0;
}
else {
flexLine._itemCount++;
indexInFlexLine++;
}
flexLine._mainSize += child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom;
flexLine._totalFlexGrow += FlexboxLayout.getFlexGrow(child);
flexLine._totalFlexShrink += FlexboxLayout.getFlexShrink(child);
flexLine._crossSize = Math.max(flexLine._crossSize, largestWidthInColumn);
// Omit divider
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
}
this._determineMainSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec);
this._determineCrossSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec, this.effectivePaddingLeft + this.effectivePaddingRight);
this._stretchViews(this.flexDirection, this.alignItems);
this._setMeasuredDimensionForFlex(this.flexDirection, widthMeasureSpec, heightMeasureSpec, childState);
}
_checkSizeConstraints(view) {
let needsMeasure = false;
let childWidth = view.getMeasuredWidth();
let childHeight = view.getMeasuredHeight();
const minWidth = view.effectiveMinWidth;
view.effectiveMinWidth = 0;
if (view.getMeasuredWidth() < minWidth) {
needsMeasure = true;
childWidth = minWidth;
}
else if (view.getMeasuredWidth() > MAX_SIZE /*lp.maxWidth*/) {
needsMeasure = true;
childWidth = MAX_SIZE /*lp.maxWidth*/;
}
const minHeight = view.effectiveMinHeight;
view.effectiveMinHeight = 0;
if (childHeight < minHeight) {
needsMeasure = true;
childHeight = minHeight;
}
else if (childHeight > MAX_SIZE /*lp.maxWidth*/) {
needsMeasure = true;
childHeight = MAX_SIZE /*lp.maxWidth*/;
}
if (needsMeasure) {
view.measure(makeMeasureSpec(childWidth, EXACTLY), makeMeasureSpec(childHeight, EXACTLY));
}
view.effectiveMinWidth = minWidth;
view.effectiveMinHeight = minHeight;
}
_addFlexLineIfLastFlexItem(childIndex, childCount, flexLine) {
if (childIndex === childCount - 1 && flexLine.itemCount !== 0) {
this._addFlexLine(flexLine);
}
}
_addFlexLine(flexLine) {
// Omit divider
this._flexLines.push(flexLine);
}
_determineMainSize(flexDirection, widthMeasureSpec, heightMeasureSpec) {
let mainSize;
let paddingAlongMainAxis;
switch (flexDirection) {
case FlexDirection.ROW:
case FlexDirection.ROW_REVERSE: {
const widthMode = getMeasureSpecMode(widthMeasureSpec);
const widthSize = getMeasureSpecSize(widthMeasureSpec);
if (widthMode === EXACTLY) {
mainSize = widthSize;
}
else {
mainSize = this._getLargestMainSize();
}
paddingAlongMainAxis = this.effectivePaddingLeft + this.effectivePaddingRight;
break;
}
case FlexDirection.COLUMN:
case FlexDirection.COLUMN_REVERSE: {
const heightMode = getMeasureSpecMode(heightMeasureSpec);
const heightSize = getMeasureSpecSize(heightMeasureSpec);
if (heightMode === EXACTLY) {
mainSize = heightSize;
}
else {
mainSize = this._getLargestMainSize();
}
paddingAlongMainAxis = this.effectivePaddingTop + this.effectivePaddingBottom;
break;
}
default:
throw new Error('Invalid flex direction: ' + flexDirection);
}
let childIndex = 0;
this._flexLines.forEach((flexLine) => {
if (flexLine.mainSize < mainSize) {
childIndex = this._expandFlexItems(flexLine, flexDirection, mainSize, paddingAlongMainAxis, childIndex);
}
else {
childIndex = this._shrinkFlexItems(flexLine, flexDirection, mainSize, paddingAlongMainAxis, childIndex);
}
});
}
_expandFlexItems(flexLine, flexDirection, maxMainSize, paddingAlongMainAxis, startIndex) {
let childIndex = startIndex;
if (flexLine._totalFlexGrow <= 0 || maxMainSize < flexLine._mainSize) {
childIndex += flexLine._itemCount;
return childIndex;
}
const sizeBeforeExpand = flexLine._mainSize;
let needsReexpand = false;
const pendingSpace = maxMainSize - flexLine._mainSize;
const unitSpace = pendingSpace / flexLine._totalFlexGrow;
flexLine._mainSize = paddingAlongMainAxis + flexLine._dividerLengthInMainSize;
let accumulatedRoundError = 0;
for (let i = 0; i < flexLine.itemCount; i++) {
const child = this._getReorderedChildAt(childIndex);
if (child === null) {
continue;
}
else if (child.isCollapsed) {
childIndex++;
continue;
}
const lp = child; // .style;
if (this._isMainAxisDirectionHorizontal(flexDirection)) {
if (!this._childrenFrozen[childIndex]) {
const flexGrow = FlexboxLayout.getFlexGrow(child);
const rawCalculatedWidth = child.getMeasuredWidth() + unitSpace * flexGrow + accumulatedRoundError;
let roundedCalculatedWidth = Math.round(rawCalculatedWidth);
// TODO: MAX_SIZE is so big, this is always false:
if (roundedCalculatedWidth > MAX_SIZE /* lp.maxWidth */) {
needsReexpand = true;
roundedCalculatedWidth = MAX_SIZE /* lp.maxWidth */;
this._childrenFrozen[childIndex] = true;
flexLine._totalFlexGrow -= flexGrow;
}
else {
accumulatedRoundError = rawCalculatedWidth - roundedCalculatedWidth;
}
child.measure(makeMeasureSpec(roundedCalculatedWidth, EXACTLY), makeMeasureSpec(child.getMeasuredHeight(), EXACTLY));
}
flexLine._mainSize += child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight;
}
else {
if (!this._childrenFrozen[childIndex]) {
const flexGrow = FlexboxLayout.getFlexGrow(child);
const rawCalculatedHeight = child.getMeasuredHeight() + unitSpace * flexGrow + accumulatedRoundError;
let roundedCalculatedHeight = Math.round(rawCalculatedHeight);
// TODO: MAX_SIZE is so big this is always false:
if (roundedCalculatedHeight > MAX_SIZE /*lp.maxHeight*/) {
needsReexpand = true;
roundedCalculatedHeight = MAX_SIZE /*lp.maxHeight*/;
this._childrenFrozen[childIndex] = true;
flexLine._totalFlexGrow -= flexGrow;
}
else {
accumulatedRoundError = rawCalculatedHeight - roundedCalculatedHeight;
}
child.measure(makeMeasureSpec(child.getMeasuredWidth(), EXACTLY), makeMeasureSpec(roundedCalculatedHeight, EXACTLY));
}
flexLine._mainSize += child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom;
}
childIndex++;
}
if (needsReexpand && sizeBeforeExpand !== flexLine._mainSize) {
this._expandFlexItems(flexLine, flexDirection, maxMainSize, paddingAlongMainAxis, startIndex);
}
return childIndex;
}
_shrinkFlexItems(flexLine, flexDirection, maxMainSize, paddingAlongMainAxis, startIndex) {
let childIndex = startIndex;
const sizeBeforeShrink = flexLine._mainSize;
if (flexLine._totalFlexShrink <= 0 || maxMainSize > flexLine._mainSize) {
childIndex += flexLine.itemCount;
return childIndex;
}
let needsReshrink = false;
const unitShrink = (flexLine._mainSize - maxMainSize) / flexLine._totalFlexShrink;
let accumulatedRoundError = 0;
flexLine._mainSize = paddingAlongMainAxis + flexLine._dividerLengthInMainSize;
for (let i = 0; i < flexLine.itemCount; i++) {
const child = this._getReorderedChildAt(childIndex);
if (child === null) {
continue;
}
else if (child.isCollapsed) {
childIndex++;
continue;
}
const lp = child; // .style;
if (this._isMainAxisDirectionHorizontal(flexDirection)) {
// The direction of main axis is horizontal
if (!this._childrenFrozen[childIndex]) {
const flexShrink = FlexboxLayout.getFlexShrink(child);
const rawCalculatedWidth = child.getMeasuredWidth() - unitShrink * flexShrink + accumulatedRoundError;
let roundedCalculatedWidth = Math.round(rawCalculatedWidth);
const minWidth = child.effectiveMinWidth;
child.effectiveMinWidth = 0;
if (roundedCalculatedWidth < minWidth) {
needsReshrink = true;
roundedCalculatedWidth = minWidth;
this._childrenFrozen[childIndex] = true;
flexLine._totalFlexShrink -= flexShrink;
}
else {
accumulatedRoundError = rawCalculatedWidth - roundedCalculatedWidth;
}
const childWidthMeasureSpec = makeMeasureSpec(roundedCalculatedWidth, EXACTLY);
// NOTE: for controls that support internal content wrapping (e.g. UILabel) reducing the width
// might result in increased height e.g. text that could be shown on one line for larger
// width needs to be wrapped in two when width is reduced.
// As a result we cannot unconditionally measure with EXACTLY the current measured height
const childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(this._currentHeightMeasureSpec, lp.effectivePaddingTop + lp.effectivePaddingBottom + lp.effectiveMarginTop + lp.effectiveMarginBottom, lp.effectiveHeight < 0 ? WRAP_CONTENT : lp.effectiveHeight);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
child.effectiveMinWidth = minWidth;
// make sure crossSize is up-to-date as child calculated height might have increased
flexLine._crossSize = Math.max(flexLine._crossSize, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom);
}
flexLine._mainSize += child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight;
}
else {
if (!this._childrenFrozen[childIndex]) {
const flexShrink = FlexboxLayout.getFlexShrink(child);
const rawCalculatedHeight = child.getMeasuredHeight() - unitShrink * flexShrink + accumulatedRoundError;
let roundedCalculatedHeight = Math.round(rawCalculatedHeight);
const minHeight = child.effectiveMinHeight;
child.effectiveMinHeight = 0;
if (roundedCalculatedHeight < minHeight) {
needsReshrink = true;
roundedCalculatedHeight = minHeight;
this._childrenFrozen[childIndex] = true;
flexLine._totalFlexShrink -= flexShrink;
}
else {
accumulatedRoundError = rawCalculatedHeight - roundedCalculatedHeight;
}
child.measure(makeMeasureSpec(child.getMeasuredWidth(), EXACTLY), makeMeasureSpec(roundedCalculatedHeight, EXACTLY));
child.effectiveMinHeight = minHeight;
}
flexLine._mainSize += child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom;
}
childIndex++;
}
if (needsReshrink && sizeBeforeShrink !== flexLine._mainSize) {
this._shrinkFlexItems(flexLine, flexDirection, maxMainSize, paddingAlongMainAxis, startIndex);
}
return childIndex;
}
_determineCrossSize(flexDirection, widthMeasureSpec, heightMeasureSpec, paddingAlongCrossAxis) {
let mode;
let size;
switch (flexDirection) {
case FlexDirection.ROW:
case FlexDirection.ROW_REVERSE:
mode = getMeasureSpecMode(heightMeasureSpec);
size = getMeasureSpecSize(heightMeasureSpec);
break;
case FlexDirection.COLUMN:
case FlexDirection.COLUMN_REVERSE:
mode = getMeasureSpecMode(widthMeasureSpec);
size = getMeasureSpecSize(widthMeasureSpec);
break;
default:
throw new Error('Invalid flex direction: ' + flexDirection);
}
if (mode === EXACTLY) {
const totalCrossSize = this._getSumOfCrossSize() + paddingAlongCrossAxis;
if (this._flexLines.length === 1) {
this._flexLines[0]._crossSize = size - paddingAlongCrossAxis;
}
else if (this._flexLines.length >= 2 && totalCrossSize < size) {
switch (this.alignContent) {
case AlignContent.STRETCH:
(() => {
const freeSpaceUnit = (size - totalCrossSize) / this._flexLines.length;
let accumulatedError = 0;
for (let i = 0, flexLinesSize = this._flexLines.length; i < flexLinesSize; i++) {
const flexLine = this._flexLines[i];
let newCrossSizeAsFloat = flexLine._crossSize + freeSpaceUnit;
if (i === this._flexLines.length - 1) {
newCrossSizeAsFloat += accumulatedError;
accumulatedError = 0;
}
let newCrossSize = Math.round(newCrossSizeAsFloat);
accumulatedError += newCrossSizeAsFloat - newCrossSize;
if (accumulatedError > 1) {
newCrossSize += 1;
accumulatedError -= 1;
}
else if (accumulatedError < -1) {
newCrossSize -= 1;
accumulatedError += 1;
}
flexLine._crossSize = newCrossSize;
}
})();
break;
case AlignContent.SPACE_AROUND:
(() => {
let spaceTopAndBottom = size - totalCrossSize;
const numberOfSpaces = this._flexLines.length * 2;
spaceTopAndBottom = spaceTopAndBottom / numberOfSpaces;
const newFlexLines = [];
const dummySpaceFlexLine = new FlexLine();
dummySpaceFlexLine._crossSize = spaceTopAndBottom;
this._flexLines.forEach((flexLine) => {
newFlexLines.push(dummySpaceFlexLine);
newFlexLines.push(flexLine);
newFlexLines.push(dummySpaceFlexLine);
});
this._flexLines = newFlexLines;
})();
break;
case AlignContent.SPACE_BETWEEN:
(() => {
let spaceBetweenFlexLine = size - totalCrossSize;
const numberOfSpaces = this._flexLines.length - 1;
spaceBetweenFlexLine = spaceBetweenFlexLine / numberOfSpaces;
let accumulatedError = 0;
const newFlexLines = [];
for (let i = 0, flexLineSize = this._flexLines.length; i < flexLineSize; i++) {
const flexLine = this._flexLines[i];
newFlexLines.push(flexLine);
if (i !== this._flexLines.length - 1) {
const dummySpaceFlexLine = new FlexLine();
if (i === this._flexLines.length - 2) {
dummySpaceFlexLine._crossSize = Math.round(spaceBetweenFlexLine + accumulatedError);
accumulatedError = 0;
}
else {
dummySpaceFlexLine._crossSize = Math.round(spaceBetweenFlexLine);
}
accumulatedError += spaceBetweenFlexLine - dummySpaceFlexLine._crossSize;
if (accumulatedError > 1) {
dummySpaceFlexLine._crossSize += 1;
accumulatedError -= 1;
}
else if (accumulatedError < -1) {
dummySpaceFlexLine._crossSize -= 1;
accumulatedError += 1;
}
newFlexLines.push(dummySpaceFlexLine);
}
}
this._flexLines = newFlexLines;
})();
break;
case AlignContent.CENTER: {
let spaceAboveAndBottom = size - totalCrossSize;
spaceAboveAndBottom = spaceAboveAndBottom / 2;
const newFlexLines = [];
const dummySpaceFlexLine = new FlexLine();
dummySpaceFlexLine._crossSize = spaceAboveAndBottom;
for (let i = 0, flexLineSize = this._flexLines.length; i < flexLineSize; i++) {
if (i === 0) {
newFlexLines.push(dummySpaceFlexLine);
}
const flexLine = this._flexLines[i];
newFlexLines.push(flexLine);
if (i === this._flexLines.length - 1) {
newFlexLines.push(dummySpaceFlexLine);
}
}
this._flexLines = newFlexLines;
break;
}
case AlignContent.FLEX_END: {
const spaceTop = size - totalCrossSize;
const dummySpaceFlexLine = new FlexLine();
dummySpaceFlexLine._crossSize = spaceTop;
this._flexLines.unshift(dummySpaceFlexLine);
break;
}
}
}
}
}
_stretchViews(flexDirection, alignItems) {
if (alignItems === AlignItems.STRETCH) {
let viewIndex = 0;
this._flexLines.forEach((flexLine) => {
for (let i = 0; i < flexLine.itemCount; i++, viewIndex++) {
const view = this._getReorderedChildAt(viewIndex);
const alignSelf = FlexboxLayout.getAlignSelf(view);
if (alignSelf !== 'auto' && alignSelf !== 'stretch') {
continue;
}
switch (flexDirection) {
case FlexDirection.ROW:
case FlexDirection.ROW_REVERSE:
this._stretchViewVertically(view, flexLine._crossSize);
break;
case FlexDirection.COLUMN:
case FlexDirection.COLUMN_REVERSE:
this._stretchViewHorizontally(view, flexLine._crossSize);
break;
default:
throw new Error('Invalid flex direction: ' + flexDirection);
}
}
});
}
else {
this._flexLines.forEach((flexLine) => {
flexLine._indicesAlignSelfStretch.forEach((index) => {
const view = this._getReorderedChildAt(index);
switch (flexDirection) {
case FlexDirection.ROW:
case FlexDirection.ROW_REVERSE:
this._stretchViewVertically(view, flexLine._crossSize);
break;
case FlexDirection.COLUMN:
case FlexDirection.COLUMN_REVERSE:
this._stretchViewHorizontally(view, flexLine._crossSize);
break;
default:
throw new Error('Invalid flex direction: ' + flexDirection);
}
});
});
}
}
_stretchViewVertically(view, crossSize) {
let newHeight = crossSize - view.effectiveMarginTop - view.effectiveMarginBottom;
newHeight = Math.max(newHeight, 0);
const originalMeasuredWidth = view.getMeasuredWidth();
let childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(this._currentWidthMeasureSpec, view.effectivePaddingLeft + view.effectivePaddingRight + view.effectiveMarginLeft + view.effectiveMarginRight, view.effectiveWidth < 0 ? WRAP_CONTENT : Math.min(view.effectiveWidth, originalMeasuredWidth));
view.measure(childWidthMeasureSpec, makeMeasureSpec(newHeight, EXACTLY));
if (originalMeasuredWidth > view.getMeasuredWidth()) {
childWidthMeasureSpec = makeMeasureSpec(originalMeasuredWidth, EXACTLY);
view.measure(childWidthMeasureSpec, makeMeasureSpec(newHeight, EXACTLY));
}
}
_stretchViewHorizontally(view, crossSize) {
let newWidth = crossSize - view.effectiveMarginLeft - view.effectiveMarginRight;
newWidth = Math.max(newWidth, 0);
view.measure(makeMeasureSpec(newWidth, EXACTLY), makeMeasureSpec(view.getMeasuredHeight(), EXACTLY));
}
_setMeasuredDimensionForFlex(flexDirection, widthMeasureSpec, heightMeasureSpec, childState) {
const widthMode = getMeasureSpecMode(widthMeasureSpec);
let widthSize = getMeasureSpecSize(widthMeasureSpec);
const heightMode = getMeasureSpecMode(heightMeasureSpec);
let heightSize = getMeasureSpecSize(heightMeasureSpec);
let calculatedMaxHeight;
let calculatedMaxWidth;
switch (flexDirection) {
case FlexDirection.ROW:
case FlexDirection.ROW_REVERSE:
calculatedMaxHeight = this._getSumOfCrossSize() + this.effectivePaddingTop + this.effectivePaddingBottom;
calculatedMaxWidth = this._getLargestMainSize();
break;
case FlexDirection.COLUMN:
case FlexDirection.COLUMN_REVERSE:
calculatedMaxHeight = this._getLargestMainSize();
calculatedMaxWidth = this._getSumOfCrossSize() + this.effectivePaddingLeft + this.effectivePaddingRight;
break;
default:
throw new Error('Invalid flex direction: ' + flexDirection);
}
let widthSizeAndState;
switch (widthMode) {
case EXACTLY:
if (widthSize < calculatedMaxWidth) {
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL);
}
widthSizeAndState = View.resolveSizeAndState(widthSize, widthSize, widthMode, childState);
break;
case AT_MOST: {
if (widthSize < calculatedMaxWidth) {
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL);
}
else {
widthSize = calculatedMaxWidth;
}
widthSizeAndState = View.resolveSizeAndState(widthSize, widthSize, widthMode, childState);
break;
}
case UNSPECIFIED: {
widthSizeAndState = View.resolveSizeAndState(calculatedMaxWidth, widthSize, widthMode, childState);
break;
}
default:
throw new Error('Unknown width mode is set: ' + widthMode);
}
let heightSizeAndState;
switch (heightMode) {
case EXACTLY:
if (heightSize < calculatedMaxHeight) {
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL >> layout.MEASURED_HEIGHT_STATE_SHIFT);
}
heightSizeAndState = View.resolveSizeAndState(heightSize, heightSize, heightMode, childState);
break;
case AT_MOST: {
if (heightSize < calculatedMaxHeight) {
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL >> layout.MEASURED_HEIGHT_STATE_SHIFT);
}
else {
heightSize = calculatedMaxHeight;
}
heightSizeAndState = View.resolveSizeAndState(heightSize, heightSize, heightMode, childState);
break;
}
case UNSPECIFIED: {
heightSizeAndState = View.resolveSizeAndState(calculatedMaxHeight, heightSize, heightMode, childState);
break;
}
default:
throw new Error('Unknown height mode is set: ' + heightMode);
}
this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
}
_isWrapRequired(child, mode, maxSize, currentLength, childLength, childAbsoluteIndex, childRelativeIndexInFlexLine) {
if (this.flexWrap === FlexWrap.NOWRAP) {
return false;
}
if (FlexboxLayout.getFlexWrapBefore(child)) {
return true;
}
if (mode === UNSPECIFIED) {
return false;
}
// Omit divider
return maxSize < currentLength + childLength;
}
_getLargestMainSize() {
return this._flexLines.reduce((max, flexLine) => Math.max(max, flexLine.mainSize), Number.MIN_VALUE);
}
_getSumOfCrossSize() {
// Omit divider
return this._flexLines.reduce((sum, flexLine) => sum + flexLine._crossSize, 0);
}
_isMainAxisDirectionHorizontal(flexDirection) {
return flexDirection === FlexDirection.ROW || flexDirection === FlexDirection.ROW_REVERSE;
}
onLayout(left, top, right, bottom) {
const insets = this.getSafeAreaInsets();
let isRtl;
switch (this.flexDirection) {
case FlexDirection.ROW:
isRtl = false;
this._layoutHorizontal(isRtl, left, top, right, bottom, insets);
break;
case FlexDirection.ROW_REVERSE:
isRtl = true;
this._layoutHorizontal(isRtl, left, top, right, bottom, insets);
break;
case FlexDirection.COLUMN:
isRtl = false;
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
isRtl = !isRtl;
}
this._layoutVertical(isRtl, false, left, top, right, bottom, insets);
break;
case FlexDirection.COLUMN_REVERSE:
isRtl = false;
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
isRtl = !isRtl;
}
this._layoutVertical(isRtl, true, left, top, right, bottom, insets);
break;
default:
throw new Error('Invalid flex direction is set: ' + this.flexDirection);
}
}
_layoutHorizontal(isRtl, left, top, right, bottom, insets) {
// include insets
const paddingLeft = this.effectivePaddingLeft + insets.left;
const paddingTop = this.effectivePaddingTop + insets.top;
const paddingRight = this.effectivePaddingRight + insets.right;
const paddingBottom = this.effectivePaddingBottom + insets.bottom;
let childLeft;
let currentViewIndex = 0;
const height = bottom - top;
const width = right - left;
// include insets
let childBottom = height - paddingBottom;
let childTop = paddingTop;
let childRight;
this._flexLines.forEach((flexLine, i) => {
// Omit divider
let spaceBetweenItem = 0.0;
switch (this.justifyContent) {
case JustifyContent.FLEX_START:
childLeft = paddingLeft;
childRight = width - paddingRight;
break;
case JustifyContent.FLEX_END:
childLeft = width - flexLine._mainSize + paddingRight;
childRight = flexLine._mainSize - paddingLeft;
break;
case JustifyContent.CENTER:
childLeft = paddingLeft + (width - insets.left - insets.right - flexLine._mainSize) / 2.0;
childRight = width - paddingRight - (width - insets.left - insets.right - flexLine._mainSize) / 2.0;
break;
case JustifyContent.SPACE_AROUND:
if (flexLine._itemCount !== 0) {
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / flexLine._itemCount;
}
childLeft = paddingLeft + spaceBetweenItem / 2.0;
childRight = width - paddingRight - spaceBetweenItem / 2.0;
break;
case JustifyContent.SPACE_BETWEEN: {
childLeft = paddingLeft;
const denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / denominator;
childRight = width - paddingRight;
break;
}
default:
throw new Error('Invalid justifyContent is set: ' + this.justifyContent);
}
spaceBetweenItem = Math.max(spaceBetweenItem, 0);
for (let j = 0; j < flexLine.itemCount; j++) {
const child = this._getReorderedChildAt(currentViewIndex);
if (child === null) {
continue;
}
else if (child.isCollapsed) {
currentViewIndex++;
continue;
}
const lp = child; // .style;
childLeft += lp.effectiveMarginLeft;
childRight -= lp.effectiveMarginRight;
// Omit divider
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
if (isRtl) {
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems, Math.round(childRight) - child.getMeasuredWidth(), childBottom - child.getMeasuredHeight(), Math.round(childRight), childBottom);
}
else {
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems, Math.round(childLeft), childBottom - child.getMeasuredHeight(), Math.round(childLeft) + child.getMeasuredWidth(), childBottom);
}
}
else {
if (isRtl) {
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems, Math.round(childRight) - child.getMeasuredWidth(), childTop, Math.round(childRight), childTop + child.getMeasuredHeight());
}
else {
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems, Math.round(childLeft), childTop, Math.round(childLeft) + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
}
}
childLeft += child.getMeasuredWidth() + spaceBetweenItem + lp.effectiveMarginRight;
childRight -= child.getMeasuredWidth() + spaceBetweenItem + lp.effectiveMarginLeft;
currentViewIndex++;
const bounds = child._getCurrentLayoutBounds();
flexLine._left = Math.min(flexLine._left, bounds.left - lp.effectiveMarginLeft);
flexLine._top = Math.min(flexLine._top, bounds.top - lp.effectiveMarginTop);
flexLine._right = Math.max(flexLine._right, bounds.right + lp.effectiveMarginRight);
flexLine._bottom = Math.max(flexLine._bottom, bounds.bottom + lp.effectiveMarginBottom);