diff --git a/apps/toolbox/src/app.css b/apps/toolbox/src/app.css
index 21ad68b04e..be681fd184 100644
--- a/apps/toolbox/src/app.css
+++ b/apps/toolbox/src/app.css
@@ -73,7 +73,7 @@ Button {
font-size: 17;
}
-.box-shadow-demo .box-shadow-prop-controls TextField{
+.box-shadow-demo .box-shadow-prop-controls TextField {
margin-left: 10;
padding: 5;
border-bottom-width: 1;
diff --git a/apps/toolbox/src/main-page.xml b/apps/toolbox/src/main-page.xml
index d8ef87cba3..bbc0245af6 100644
--- a/apps/toolbox/src/main-page.xml
+++ b/apps/toolbox/src/main-page.xml
@@ -14,6 +14,7 @@
+
diff --git a/apps/toolbox/src/pages/gutters.ts b/apps/toolbox/src/pages/gutters.ts
new file mode 100644
index 0000000000..502d195d09
--- /dev/null
+++ b/apps/toolbox/src/pages/gutters.ts
@@ -0,0 +1,10 @@
+import { fromObject, EventData, Page } from '@nativescript/core';
+
+export function navigatingTo(args: EventData) {
+ const page = args.object;
+ page.bindingContext = fromObject({
+ rowGap: 16,
+ columnGap: 16,
+ gap: 16,
+ });
+}
diff --git a/apps/toolbox/src/pages/gutters.xml b/apps/toolbox/src/pages/gutters.xml
new file mode 100644
index 0000000000..0af6f730d5
--- /dev/null
+++ b/apps/toolbox/src/pages/gutters.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core/debugger/dom-types.ts b/packages/core/debugger/dom-types.ts
index 6face9719d..f0bc0003eb 100644
--- a/packages/core/debugger/dom-types.ts
+++ b/packages/core/debugger/dom-types.ts
@@ -5,7 +5,7 @@ import { PercentLength } from '../ui/styling/length-shared';
import { getSetProperties, getComputedCssValues } from '../ui/core/properties';
const ELEMENT_NODE_TYPE = 1;
const ROOT_NODE_TYPE = 9;
-const propertyBlacklist = ['effectivePaddingLeft', 'effectivePaddingBottom', 'effectivePaddingRight', 'effectivePaddingTop', 'effectiveBorderTopWidth', 'effectiveBorderRightWidth', 'effectiveBorderBottomWidth', 'effectiveBorderLeftWidth', 'effectiveMinWidth', 'effectiveMinHeight', 'effectiveWidth', 'effectiveHeight', 'effectiveMarginLeft', 'effectiveMarginTop', 'effectiveMarginRight', 'effectiveMarginBottom', 'nodeName', 'nodeType', 'decodeWidth', 'decodeHeight', 'ng-reflect-items', 'domNode', 'touchListenerIsSet', 'bindingContext', 'nativeView'];
+const propertyBlacklist = ['effectivePaddingLeft', 'effectivePaddingBottom', 'effectivePaddingRight', 'effectivePaddingTop', 'effectiveBorderTopWidth', 'effectiveBorderRightWidth', 'effectiveBorderBottomWidth', 'effectiveBorderLeftWidth', 'effectiveMinWidth', 'effectiveMinHeight', 'effectiveWidth', 'effectiveHeight', 'effectiveMarginLeft', 'effectiveMarginTop', 'effectiveMarginRight', 'effectiveMarginBottom', 'effectiveRowGap', 'effectiveColumnGap', 'nodeName', 'nodeType', 'decodeWidth', 'decodeHeight', 'ng-reflect-items', 'domNode', 'touchListenerIsSet', 'bindingContext', 'nativeView'];
function lazy(action: () => T): () => T {
let _value: T;
diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar
index 4fd60d4bd8..d3e6b537d3 100644
Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ
diff --git a/packages/core/ui/core/view-base/index.ts b/packages/core/ui/core/view-base/index.ts
index 99f866fbf8..9433fa1248 100644
--- a/packages/core/ui/core/view-base/index.ts
+++ b/packages/core/ui/core/view-base/index.ts
@@ -542,6 +542,8 @@ export abstract class ViewBase extends Observable {
public effectiveBorderRightWidth: number;
public effectiveBorderBottomWidth: number;
public effectiveBorderLeftWidth: number;
+ public effectiveRowGap: number;
+ public effectiveColumnGap: number;
/**
* @private
@@ -1569,6 +1571,8 @@ ViewBase.prototype.effectiveBorderTopWidth = 0;
ViewBase.prototype.effectiveBorderRightWidth = 0;
ViewBase.prototype.effectiveBorderBottomWidth = 0;
ViewBase.prototype.effectiveBorderLeftWidth = 0;
+ViewBase.prototype.effectiveRowGap = 0;
+ViewBase.prototype.effectiveColumnGap = 0;
ViewBase.prototype._isViewBase = true;
ViewBase.prototype.recycleNativeView = 'never';
ViewBase.prototype.reusable = false;
diff --git a/packages/core/ui/layouts/flexbox-layout/flexbox-layout-common.ts b/packages/core/ui/layouts/flexbox-layout/flexbox-layout-common.ts
index 95ff911420..e9e7cda3cd 100644
--- a/packages/core/ui/layouts/flexbox-layout/flexbox-layout-common.ts
+++ b/packages/core/ui/layouts/flexbox-layout/flexbox-layout-common.ts
@@ -3,6 +3,7 @@ import { View, CSSType } from '../../core/view';
import { CssProperty, ShorthandProperty, makeParser, makeValidator } from '../../core/properties';
import { unsetValue } from '../../core/properties/property-shared';
import { Style } from '../../styling/style';
+import { CoreTypes } from '../../../core-types';
export type Basis = 'auto' | number;
@@ -181,6 +182,27 @@ export abstract class FlexboxLayoutBase extends LayoutBase {
this.style.alignContent = value;
}
+ get gap(): string | CoreTypes.LengthType {
+ return this.style.gap;
+ }
+ set gap(value: string | CoreTypes.LengthType) {
+ this.style.gap = value;
+ }
+
+ get rowGap(): CoreTypes.LengthType {
+ return this.style.rowGap;
+ }
+ set rowGap(value: CoreTypes.LengthType) {
+ this.style.rowGap = value;
+ }
+
+ get columnGap(): CoreTypes.LengthType {
+ return this.style.columnGap;
+ }
+ set columnGap(value: CoreTypes.LengthType) {
+ this.style.columnGap = value;
+ }
+
public static setOrder(view: View, order: number) {
validateArgs(view).style.order = order;
}
diff --git a/packages/core/ui/layouts/flexbox-layout/index.android.ts b/packages/core/ui/layouts/flexbox-layout/index.android.ts
index 2289af0686..138d9b606c 100644
--- a/packages/core/ui/layouts/flexbox-layout/index.android.ts
+++ b/packages/core/ui/layouts/flexbox-layout/index.android.ts
@@ -2,6 +2,7 @@ import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Flex
import { CoreTypes } from '../../../core-types';
import { View } from '../../core/view';
import { Length } from '../../styling/length-shared';
+import { columnGapProperty, rowGapProperty } from '../../styling/style-properties';
export * from './flexbox-layout-common';
@@ -132,6 +133,22 @@ export class FlexboxLayout extends FlexboxLayoutBase {
this.nativeViewProtected.setAlignContent(alignContentMap[alignContent]);
}
+ [rowGapProperty.getDefault](): CoreTypes.LengthType {
+ return rowGapProperty.defaultValue;
+ }
+ [rowGapProperty.setNative](value: CoreTypes.LengthType) {
+ const gap = value ? Length.toDevicePixels(value, 0) : 0;
+ this.nativeViewProtected.setRowGap(Math.max(gap, 0));
+ }
+
+ [columnGapProperty.getDefault](): CoreTypes.LengthType {
+ return columnGapProperty.defaultValue;
+ }
+ [columnGapProperty.setNative](value: CoreTypes.LengthType) {
+ const gap = value ? Length.toDevicePixels(value, 0) : 0;
+ this.nativeViewProtected.setColumnGap(Math.max(gap, 0));
+ }
+
public _updateNativeLayoutParams(child: View): void {
super._updateNativeLayoutParams(child);
diff --git a/packages/core/ui/layouts/flexbox-layout/index.d.ts b/packages/core/ui/layouts/flexbox-layout/index.d.ts
index d73626d92b..30bee53af3 100644
--- a/packages/core/ui/layouts/flexbox-layout/index.d.ts
+++ b/packages/core/ui/layouts/flexbox-layout/index.d.ts
@@ -2,7 +2,7 @@ import { LayoutBase } from '../layout-base';
import { Style } from '../../styling/style';
import { CssProperty } from '../../core/properties';
import { View } from '../../core/view';
-import { CoreTypes } from '../../enums';
+import { CoreTypes } from '../../../core-types';
export type FlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse';
export type FlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse';
@@ -68,6 +68,24 @@ export class FlexboxLayout extends LayoutBase {
* @nsProperty
*/
public alignContent: AlignContent;
+ /**
+ * Gets or sets the gaps between rows and columns
+ *
+ * @nsProperty
+ */
+ public gap: string | CoreTypes.LengthType;
+ /**
+ * Gets or sets the gap between rows
+ *
+ * @nsProperty
+ */
+ public rowGap: CoreTypes.LengthType;
+ /**
+ * Gets or sets the gap between columns
+ *
+ * @nsProperty
+ */
+ public columnGap: CoreTypes.LengthType;
public static setOrder(view: View, order: number);
public static getOrder(view: View): number;
diff --git a/packages/core/ui/layouts/flexbox-layout/index.ios.ts b/packages/core/ui/layouts/flexbox-layout/index.ios.ts
index 86fca52268..6c15d2ae6a 100644
--- a/packages/core/ui/layouts/flexbox-layout/index.ios.ts
+++ b/packages/core/ui/layouts/flexbox-layout/index.ios.ts
@@ -2,36 +2,37 @@ import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Flex
import { View } from '../../core/view';
import { Position } from '../../core/view/view-interfaces';
import { layout } from '../../../utils';
+import { CoreTypes } from '../../../core-types';
export * from './flexbox-layout-common';
-import EXACTLY = layout.EXACTLY;
-import AT_MOST = layout.AT_MOST;
-import UNSPECIFIED = layout.UNSPECIFIED;
+const EXACTLY = layout.EXACTLY;
+const AT_MOST = layout.AT_MOST;
+const UNSPECIFIED = layout.UNSPECIFIED;
-import MEASURED_STATE_TOO_SMALL = layout.MEASURED_STATE_TOO_SMALL;
+const MEASURED_STATE_TOO_SMALL = layout.MEASURED_STATE_TOO_SMALL;
+
+const MATCH_PARENT = -1;
+const WRAP_CONTENT = -2;
+
+const View_sUseZeroUnspecifiedMeasureSpec = true; // NOTE: android version < M
+
+const makeMeasureSpec = layout.makeMeasureSpec;
+const getMeasureSpecMode = layout.getMeasureSpecMode;
+const getMeasureSpecSize = layout.getMeasureSpecSize;
-function requestFlexboxLayout(this: View, 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
-
-import makeMeasureSpec = layout.makeMeasureSpec;
-import getMeasureSpecMode = layout.getMeasureSpecMode;
-import getMeasureSpecSize = layout.getMeasureSpecSize;
-import { CoreTypes } from '../../enums';
+function requestFlexboxLayout(this: View, _value) {
+ const flexbox = this.parent;
+ if (flexbox instanceof FlexboxLayoutBase) {
+ flexbox.requestLayout();
+ }
+}
// `eachLayoutChild` iterates over children, and we need more - indexed access.
// This class tries to accomodate that by collecting all children in an
@@ -62,7 +63,7 @@ class FlexLine {
_bottom: number = Number.MAX_VALUE;
_mainSize = 0;
- _dividerLengthInMainSize = 0;
+ _gapLengthInMainSize = 0;
_crossSize = 0;
_itemCount = 0;
_goneItemCount = 0;
@@ -119,8 +120,6 @@ class Order {
}
export class FlexboxLayout extends FlexboxLayoutBase {
- // Omit divider
-
private _reorderedIndices: number[];
private _orderCache: number[];
@@ -174,18 +173,22 @@ export class FlexboxLayout extends FlexboxLayoutBase {
return this._sortOrdersIntoReorderedIndices(childCount, orders);
}
- private _sortOrdersIntoReorderedIndices(childCount: number, orders: Order[]): number[] {
+ private _sortOrdersIntoReorderedIndices(_childCount: number, orders: Order[]): number[] {
orders.sort((a, b) => a.compareTo(b));
if (!this._orderCache) {
this._orderCache = [];
+ } else {
+ this._orderCache.length = 0;
}
- this._orderCache.length = 0;
const reorderedIndices: number[] = [];
- orders.forEach((order, i) => {
+
+ for (let i = 0, length = orders.length; i < length; i++) {
+ const order = orders[i];
+
reorderedIndices[i] = order.index;
- this._orderCache[i] = order.order;
- });
+ this._orderCache[order.index] = order.order;
+ }
return reorderedIndices;
}
@@ -235,102 +238,105 @@ export class FlexboxLayout extends FlexboxLayoutBase {
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++;
- flexLine._goneItemCount++;
- this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
- continue;
- }
+ const childCount = this.measureContext.childrenCount;
+ const paddingStart = FlexboxLayout.getPaddingStart(this);
+ const paddingEnd = FlexboxLayout.getPaddingEnd(this);
+ let largestHeightInRow = Number.MIN_VALUE;
+ let flexLine = new FlexLine();
- child._updateEffectiveLayoutValues(widthSize, widthMode, heightSize, heightMode);
- const lp = child; // child.style;
- if (FlexboxLayout.getAlignSelf(child) === 'stretch') {
- flexLine._indicesAlignSelfStretch.push(i);
- }
+ 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++;
+ flexLine._goneItemCount++;
+ this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
+ continue;
+ }
- let childWidth = lp.effectiveWidth;
- if (FlexBasisPercent.DEFAULT /*lp.flexBasisPercent*/ !== FlexBasisPercent.DEFAULT && widthMode === EXACTLY) {
- childWidth = Math.round(widthSize * FlexBasisPercent.DEFAULT /*lp.flexBasisPercent*/);
- }
+ child._updateEffectiveLayoutValues(widthSize, widthMode, heightSize, heightMode);
+ const lp = child; // child.style;
+ if (FlexboxLayout.getAlignSelf(child) === 'stretch') {
+ flexLine._indicesAlignSelfStretch.push(i);
+ }
- const childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(widthMeasureSpec, lp.effectivePaddingLeft + lp.effectivePaddingRight + lp.effectiveMarginLeft + lp.effectiveMarginRight, childWidth < 0 ? WRAP_CONTENT : childWidth);
+ let childWidth = lp.effectiveWidth;
+ if (FlexBasisPercent.DEFAULT /*lp.flexBasisPercent*/ !== FlexBasisPercent.DEFAULT && widthMode === EXACTLY) {
+ childWidth = Math.round(widthSize * FlexBasisPercent.DEFAULT /*lp.flexBasisPercent*/);
+ }
- const childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(heightMeasureSpec, lp.effectivePaddingTop + lp.effectivePaddingBottom + lp.effectiveMarginTop + lp.effectiveMarginBottom, lp.effectiveHeight < 0 ? WRAP_CONTENT : lp.effectiveHeight);
+ const childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(widthMeasureSpec, lp.effectivePaddingLeft + lp.effectivePaddingRight + lp.effectiveMarginLeft + lp.effectiveMarginRight, childWidth < 0 ? WRAP_CONTENT : childWidth);
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ const childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(heightMeasureSpec, lp.effectivePaddingTop + lp.effectivePaddingBottom + lp.effectiveMarginTop + lp.effectiveMarginBottom, lp.effectiveHeight < 0 ? WRAP_CONTENT : lp.effectiveHeight);
- this._checkSizeConstraints(child);
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- childState = View.combineMeasuredStates(childState, child.getMeasuredState());
- largestHeightInRow = Math.max(largestHeightInRow, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom);
+ this._checkSizeConstraints(child);
- if (this._isWrapRequired(child, widthMode, widthSize, flexLine._mainSize, child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight, i, indexInFlexLine)) {
- if (flexLine.layoutVisibleItemCount > 0) {
- this._addFlexLine(flexLine);
- }
+ childState = View.combineMeasuredStates(childState, child.getMeasuredState());
+ largestHeightInRow = Math.max(largestHeightInRow, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom);
- flexLine = new FlexLine();
- flexLine._itemCount = 1;
- flexLine._mainSize = paddingStart + paddingEnd;
- largestHeightInRow = child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom;
- indexInFlexLine = 0;
- } else {
- flexLine._itemCount++;
- indexInFlexLine++;
+ if (this._isWrapRequired(child, widthMode, widthSize, flexLine._mainSize, child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight, i, indexInFlexLine)) {
+ if (flexLine.layoutVisibleItemCount > 0) {
+ this._addFlexLine(flexLine);
}
- 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);
+ 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);
- // Omit divider
+ flexLine._crossSize = Math.max(flexLine._crossSize, largestHeightInRow);
- 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);
+ // Check if column gap is required for the flex item
+ if (this.effectiveColumnGap > 0 && this._hasPrecedingViews(i, indexInFlexLine)) {
+ flexLine._mainSize += this.effectiveColumnGap;
+ flexLine._gapLengthInMainSize += this.effectiveColumnGap;
}
- })();
+
+ 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) => {
+
+ for (const fl of this._flexLines) {
let largestHeightInLine = Number.MIN_VALUE;
- for (let i = viewIndex; i < viewIndex + flexLine._itemCount; i++) {
+ for (let i = viewIndex; i < viewIndex + fl._itemCount; i++) {
const child = this._getReorderedChildAt(i);
const lp = child; // .style;
if (this.flexWrap !== FlexWrap.WRAP_REVERSE) {
- let marginTop = flexLine._maxBaseline - FlexboxLayout.getBaseline(child);
+ let marginTop = fl._maxBaseline - FlexboxLayout.getBaseline(child);
marginTop = Math.max(marginTop, lp.effectiveMarginTop);
largestHeightInLine = Math.max(largestHeightInLine, child.getMeasuredHeight() + marginTop + lp.effectiveMarginBottom);
} else {
- let marginBottom = flexLine._maxBaseline - child.getMeasuredHeight() + FlexboxLayout.getBaseline(child);
+ let marginBottom = fl._maxBaseline - child.getMeasuredHeight() + FlexboxLayout.getBaseline(child);
marginBottom = Math.max(marginBottom, lp.effectiveMarginBottom);
largestHeightInLine = Math.max(largestHeightInLine, child.getMeasuredHeight() + lp.effectiveMarginTop + marginBottom);
}
}
- flexLine._crossSize = largestHeightInLine;
- viewIndex += flexLine.itemCount;
- });
+ fl._crossSize = largestHeightInLine;
+ viewIndex += fl.itemCount;
+ }
}
this._determineCrossSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec, this.effectivePaddingTop + this.effectivePaddingBottom);
@@ -409,7 +415,10 @@ export class FlexboxLayout extends FlexboxLayoutBase {
flexLine._crossSize = Math.max(flexLine._crossSize, largestWidthInColumn);
- // Omit divider
+ if (this.effectiveRowGap > 0 && this._hasPrecedingViews(i, indexInFlexLine)) {
+ flexLine._mainSize += this.effectiveRowGap;
+ }
+
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
}
@@ -457,7 +466,6 @@ export class FlexboxLayout extends FlexboxLayoutBase {
}
private _addFlexLine(flexLine: FlexLine) {
- // Omit divider
this._flexLines.push(flexLine);
}
@@ -495,13 +503,13 @@ export class FlexboxLayout extends FlexboxLayoutBase {
}
let childIndex = 0;
- this._flexLines.forEach((flexLine) => {
+ for (const flexLine of this._flexLines) {
if (flexLine.mainSize < mainSize) {
childIndex = this._expandFlexItems(flexLine, flexDirection, mainSize, paddingAlongMainAxis, childIndex);
} else {
childIndex = this._shrinkFlexItems(flexLine, flexDirection, mainSize, paddingAlongMainAxis, childIndex);
}
- });
+ }
}
private _expandFlexItems(flexLine: FlexLine, flexDirection: FlexDirection, maxMainSize: number, paddingAlongMainAxis: number, startIndex: number) {
@@ -515,7 +523,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
let needsReexpand = false;
const pendingSpace = maxMainSize - flexLine._mainSize;
const unitSpace = pendingSpace / flexLine._totalFlexGrow;
- flexLine._mainSize = paddingAlongMainAxis + flexLine._dividerLengthInMainSize;
+ flexLine._mainSize = paddingAlongMainAxis + flexLine._gapLengthInMainSize;
let accumulatedRoundError = 0;
for (let i = 0; i < flexLine.itemCount; i++) {
const child = this._getReorderedChildAt(childIndex);
@@ -582,7 +590,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
let needsReshrink = false;
const unitShrink = (flexLine._mainSize - maxMainSize) / flexLine._totalFlexShrink;
let accumulatedRoundError = 0;
- flexLine._mainSize = paddingAlongMainAxis + flexLine._dividerLengthInMainSize;
+ flexLine._mainSize = paddingAlongMainAxis + flexLine._gapLengthInMainSize;
for (let i = 0; i < flexLine.itemCount; i++) {
const child = this._getReorderedChildAt(childIndex);
if (child === null) {
@@ -677,79 +685,79 @@ export class FlexboxLayout extends FlexboxLayoutBase {
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;
+ 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;
+ const newFlexLines: FlexLine[] = [];
+ const dummySpaceFlexLine = new FlexLine();
+
+ spaceTopAndBottom = spaceTopAndBottom / numberOfSpaces;
+ dummySpaceFlexLine._crossSize = spaceTopAndBottom;
+
+ for (const flexLine of this._flexLines) {
+ 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: FlexLine[] = [];
+ 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);
}
- let newCrossSize = Math.round(newCrossSizeAsFloat);
- accumulatedError += newCrossSizeAsFloat - newCrossSize;
+ accumulatedError += spaceBetweenFlexLine - dummySpaceFlexLine._crossSize;
if (accumulatedError > 1) {
- newCrossSize += 1;
+ dummySpaceFlexLine._crossSize += 1;
accumulatedError -= 1;
} else if (accumulatedError < -1) {
- newCrossSize -= 1;
+ dummySpaceFlexLine._crossSize -= 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: FlexLine[] = [];
- 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: FlexLine[] = [];
- 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;
- })();
+ }
+ this._flexLines = newFlexLines;
break;
+ }
case AlignContent.CENTER: {
let spaceAboveAndBottom = size - totalCrossSize;
spaceAboveAndBottom = spaceAboveAndBottom / 2;
@@ -784,7 +792,8 @@ export class FlexboxLayout extends FlexboxLayoutBase {
private _stretchViews(flexDirection: FlexDirection, alignItems: AlignItems) {
if (alignItems === AlignItems.STRETCH) {
let viewIndex = 0;
- this._flexLines.forEach((flexLine) => {
+
+ for (const flexLine of this._flexLines) {
for (let i = 0; i < flexLine.itemCount; i++, viewIndex++) {
const view = this._getReorderedChildAt(viewIndex);
const alignSelf = FlexboxLayout.getAlignSelf(view);
@@ -804,10 +813,10 @@ export class FlexboxLayout extends FlexboxLayoutBase {
throw new Error('Invalid flex direction: ' + flexDirection);
}
}
- });
+ }
} else {
- this._flexLines.forEach((flexLine) => {
- flexLine._indicesAlignSelfStretch.forEach((index) => {
+ for (const flexLine of this._flexLines) {
+ for (const index of flexLine._indicesAlignSelfStretch) {
const view = this._getReorderedChildAt(index);
switch (flexDirection) {
case FlexDirection.ROW:
@@ -821,8 +830,8 @@ export class FlexboxLayout extends FlexboxLayoutBase {
default:
throw new Error('Invalid flex direction: ' + flexDirection);
}
- });
- });
+ }
+ }
}
}
@@ -924,14 +933,24 @@ export class FlexboxLayout extends FlexboxLayoutBase {
if (this.flexWrap === FlexWrap.NOWRAP) {
return false;
}
+
if (FlexboxLayout.getFlexWrapBefore(child)) {
return true;
}
+
if (mode === UNSPECIFIED) {
return false;
}
- // Omit divider
+ if (this._isMainAxisDirectionHorizontal(this.flexDirection)) {
+ if (this.effectiveColumnGap > 0 && this._hasPrecedingViews(childAbsoluteIndex, childRelativeIndexInFlexLine)) {
+ childLength += this.effectiveColumnGap;
+ }
+ } else {
+ if (this.effectiveRowGap > 0 && this._hasPrecedingViews(childAbsoluteIndex, childRelativeIndexInFlexLine)) {
+ childLength += this.effectiveRowGap;
+ }
+ }
return maxSize < currentLength + childLength;
}
@@ -941,8 +960,21 @@ export class FlexboxLayout extends FlexboxLayoutBase {
}
private _getSumOfCrossSize(): number {
- // Omit divider
- return this._flexLines.reduce((sum, flexLine) => sum + flexLine._crossSize, 0);
+ const isHorizontalDirection = this._isMainAxisDirectionHorizontal(this.flexDirection);
+ let sum: number = 0;
+
+ for (let i = 0, length = this._flexLines.length; i < length; i++) {
+ const flexLine = this._flexLines[i];
+ const gap = isHorizontalDirection ? this.effectiveRowGap : this.effectiveColumnGap;
+
+ // Judge if gap is required
+ if (gap > 0 && this._hasPrecedingFlexLines(i)) {
+ sum += gap;
+ }
+ sum += flexLine._crossSize;
+ }
+
+ return sum;
}
private _isMainAxisDirectionHorizontal(flexDirection: FlexDirection): boolean {
@@ -993,10 +1025,15 @@ export class FlexboxLayout extends FlexboxLayoutBase {
// include insets
let childBottom = height - paddingBottom;
let childTop = paddingTop;
-
let childRight;
- this._flexLines.forEach((flexLine, i) => {
- // Omit divider
+
+ for (let i = 0, length = this._flexLines.length; i < length; i++) {
+ const flexLine = this._flexLines[i];
+
+ if (this.effectiveRowGap > 0 && this._hasPrecedingFlexLines(i)) {
+ childBottom -= this.effectiveRowGap;
+ childTop += this.effectiveRowGap;
+ }
let spaceBetweenItem = 0.0;
switch (this.justifyContent) {
@@ -1047,7 +1084,10 @@ export class FlexboxLayout extends FlexboxLayoutBase {
childLeft += lp.effectiveMarginLeft;
childRight -= lp.effectiveMarginRight;
- // Omit divider
+ if (this.effectiveColumnGap > 0 && this._hasPrecedingViews(currentViewIndex, j)) {
+ childLeft += this.effectiveColumnGap;
+ childRight -= this.effectiveColumnGap;
+ }
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
if (isRtl) {
@@ -1075,7 +1115,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
childTop += flexLine._crossSize;
childBottom -= flexLine._crossSize;
- });
+ }
}
private _layoutSingleChildHorizontal(view: View, flexLine: FlexLine, flexWrap: FlexWrap, alignItems: AlignItems, left: number, top: number, right: number, bottom: number): void {
@@ -1142,8 +1182,13 @@ export class FlexboxLayout extends FlexboxLayoutBase {
let childTop;
let childBottom;
- this._flexLines.forEach((flexLine) => {
- // Omit divider.
+ for (let i = 0, length = this._flexLines.length; i < length; i++) {
+ const flexLine = this._flexLines[i];
+
+ if (this.effectiveColumnGap > 0 && this._hasPrecedingFlexLines(i)) {
+ childLeft += this.effectiveColumnGap;
+ childRight -= this.effectiveColumnGap;
+ }
let spaceBetweenItem = 0.0;
@@ -1195,7 +1240,10 @@ export class FlexboxLayout extends FlexboxLayoutBase {
childTop += lp.effectiveMarginTop;
childBottom -= lp.effectiveMarginBottom;
- // Omit divider.
+ if (this.effectiveRowGap > 0 && this._hasPrecedingViews(currentViewIndex, j)) {
+ childTop += this.effectiveRowGap;
+ childBottom -= this.effectiveRowGap;
+ }
if (isRtl) {
if (fromBottomToTop) {
@@ -1223,7 +1271,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
childLeft += flexLine.crossSize;
childRight -= flexLine.crossSize;
- });
+ }
}
private _layoutSingleChildVertical(view: View, flexLine: FlexLine, isRtl: boolean, alignItems: AlignItems, left: number, top: number, right: number, bottom: number) {
@@ -1264,7 +1312,29 @@ export class FlexboxLayout extends FlexboxLayoutBase {
}
}
- // Omit divider in onDraw(), drawDividersHorizontal, drawDividersVertical, drawVerticalDivider
+ private _hasPrecedingViews(childAbsoluteIndex: number, childRelativeIndexInFlexLine: number): boolean {
+ for (let i = 1; i <= childRelativeIndexInFlexLine; i++) {
+ const view = this._getReorderedChildAt(childAbsoluteIndex - i);
+ if (view != null && !view.isCollapsed) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private _hasPrecedingFlexLines(flexLineIndex: number): boolean {
+ if (flexLineIndex < 0 || flexLineIndex >= this._flexLines.length) {
+ return false;
+ }
+
+ for (let i = 0; i < flexLineIndex; i++) {
+ if (this._flexLines[i].layoutVisibleItemCount > 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
// requestLayout on set flexDirection, set flexWrap, set justifyContent, set alignItems, set alignContent
diff --git a/packages/core/ui/styling/style-properties.ts b/packages/core/ui/styling/style-properties.ts
index 36db9115b2..be880fef08 100644
--- a/packages/core/ui/styling/style-properties.ts
+++ b/packages/core/ui/styling/style-properties.ts
@@ -1,11 +1,10 @@
import { CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties';
-import { unsetValue, isCssWideKeyword } from '../core/properties/property-shared';
+import { unsetValue } from '../core/properties/property-shared';
import { Style } from './style';
import { Color } from '../../color';
import { Font, parseFont, FontStyle, FontStyleType, FontWeight, FontWeightType, FontVariationSettings, FontVariationSettingsType } from './font';
import { Background } from './background';
-import { layout } from '../../utils';
import { Trace } from '../../trace';
import { CoreTypes } from '../../core-types';
@@ -25,76 +24,9 @@ interface ShorthandPositioning {
left: string;
}
-function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
-function equalsCommon(a: CoreTypes.PercentLengthType, b: CoreTypes.PercentLengthType): boolean;
-function equalsCommon(a: CoreTypes.PercentLengthType, b: CoreTypes.PercentLengthType | CoreTypes.LengthDipUnit): boolean {
- if (a == 'auto' || isCssWideKeyword(a)) {
- return b == 'auto' || isCssWideKeyword(b);
- }
-
- if (b == 'auto' || isCssWideKeyword(b)) {
- return false;
- }
-
- if (typeof a === 'number') {
- if (typeof b === 'number') {
- return a == b;
- }
- if (!b) {
- return false;
- }
- return (b as CoreTypes.LengthDipUnit).unit == 'dip' && a == (b as CoreTypes.LengthDipUnit).value;
- }
-
- if (typeof b === 'number') {
- return a ? (a as CoreTypes.LengthDipUnit).unit == 'dip' && (a as CoreTypes.LengthDipUnit).value == b : false;
- }
- if (!a || !b) {
- return false;
- }
- return (a as CoreTypes.LengthDipUnit).value == (b as CoreTypes.LengthDipUnit).value && (a as CoreTypes.LengthDipUnit).unit == (b as CoreTypes.LengthDipUnit).unit;
-}
-
-function convertToStringCommon(length: CoreTypes.LengthType | CoreTypes.PercentLengthType): string {
- if (length == 'auto' || isCssWideKeyword(length)) {
- return 'auto';
- }
-
- if (typeof length === 'number') {
- return length.toString();
- }
-
- let val = (length as CoreTypes.LengthPercentUnit).value;
- if ((length as CoreTypes.LengthPercentUnit).unit === '%') {
- val *= 100;
- }
-
- return val + (length as CoreTypes.LengthPercentUnit).unit;
-}
-
-function toDevicePixelsCommon(length: CoreTypes.PercentLengthType, auto: number = Number.NaN, parentAvailableWidth: number = Number.NaN): number {
- if (length == 'auto' || isCssWideKeyword(length)) {
- return auto;
- }
- if (typeof length === 'number') {
- return layout.round(layout.toDevicePixels(length));
- }
- if (!length) {
- return auto;
- }
- // @ts-ignore
- switch (length.unit) {
- case 'px':
- // @ts-ignore
- return layout.round(length.value);
- case '%':
- // @ts-ignore
- return layout.round(parentAvailableWidth * length.value);
- case 'dip':
- default:
- // @ts-ignore
- return layout.round(layout.toDevicePixels(length.value));
- }
+interface ShorthandGap {
+ row: string;
+ col: string;
}
function isNonNegativeFiniteNumber(value: number): boolean {
@@ -169,6 +101,28 @@ function parseShorthandPositioning(value: string): ShorthandPositioning {
};
}
+function parseShorthandGap(value: string): ShorthandGap {
+ const arr = value.split(/[ ,]+/);
+
+ let row: string;
+ let col: string;
+
+ if (arr.length === 1) {
+ row = arr[0];
+ col = arr[0];
+ } else if (arr.length === 2) {
+ row = arr[0];
+ col = arr[1];
+ } else {
+ throw new Error('Expected 1 or 2 parameters. Actual: ' + value);
+ }
+
+ return {
+ row,
+ col,
+ };
+}
+
function parseBorderColorPositioning(value: string): ShorthandPositioning {
if (value.indexOf('rgb') === 0 || value.indexOf('hsl') === 0) {
return {
@@ -256,6 +210,30 @@ function convertToPaddings(value: string | CoreTypes.LengthType): [CssProperty, CoreTypes.LengthType][] {
+ let rowGap: CoreTypes.LengthType;
+ let colGap: CoreTypes.LengthType;
+
+ if (typeof value === 'string' && value !== 'auto') {
+ if (value.length) {
+ const gaps = parseShorthandGap(value);
+ rowGap = Length.parse(gaps.row);
+ colGap = Length.parse(gaps.col);
+ } else {
+ rowGap = 0;
+ colGap = 0;
+ }
+ } else {
+ rowGap = value;
+ colGap = value;
+ }
+
+ return [
+ [rowGapProperty, rowGap],
+ [columnGapProperty, colGap],
+ ];
+}
+
function convertToTransform(value: string): [CssAnimationProperty, any][] {
if (value === unsetValue) {
value = 'none';
@@ -526,6 +504,56 @@ export const paddingBottomProperty = new CssProperty