Merge pull request #185 from rigdern/rigdern/spec-conformance

Alter layout engine to conform closer to W3C spec
This commit is contained in:
Emil Sjölander
2016-05-31 11:04:41 +01:00
36 changed files with 10625 additions and 4200 deletions

View File

@@ -92,10 +92,19 @@ borderWidth, borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidt
flexDirection | 'column', 'row'
justifyContent | 'flex-start', 'center', 'flex-end', 'space-between', 'space-around'
alignItems, alignSelf | 'flex-start', 'center', 'flex-end', 'stretch'
flex | positive number
flex | number
flexWrap | 'wrap', 'nowrap'
position | 'relative', 'absolute'
overflow | 'visible', 'hidden'
- Rather than allowing arbitrary combinations of `flex-grow`, `flex-shrink`, and `flex-basis` the implementation only supports a few common combinations expressed as a single number using the `flex` attribute:
css-layout `flex` value | W3C `flex` short-hand equivalent
---|---
n (where n > 0) | n 0 0
0 | 0 0 auto
-1 | 0 1 auto
- `inherit` value is not implemented because it's a way to disambiguate between multiple colliding rules. This should be done in a pre-processing step, not in the actual layout algorithm.
@@ -118,6 +127,7 @@ div, span {
border: 0 solid black;
margin: 0;
padding: 0;
min-width: 0;
}
```

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--This file represents the results of running a test suite-->
<test-results name="src/csharp/Facebook.CSSLayout.Tests/bin/Release/Facebook.CSSLayout.Tests.dll" total="0" failures="0" not-run="0" date="2016-04-01" time="11:01:04">
<environment nunit-version="2.4.8.0" clr-version="4.0.30319.17020" os-version="Unix 15.4.0.0" platform="Unix" cwd="/Users/emilsj/css-layout" machine-name="emilsj-pro" user="emilsj" user-domain="emilsj-pro" />
<culture-info current-culture="en-US" current-uiculture="en-US" />
<test-suite name="src/csharp/Facebook.CSSLayout.Tests/bin/Release/Facebook.CSSLayout.Tests.dll" success="True" time="0.001" asserts="0">
<results />
</test-suite>
</test-results>

1851
dist/css-layout.h vendored

File diff suppressed because it is too large Load Diff

BIN
dist/css-layout.jar vendored

Binary file not shown.

1691
dist/css-layout.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,11 +9,18 @@
function __transpileToCSharpCommon(code) {
return code
.replace(/'abs-layout'/g, '"abs-layout"')
.replace(/'abs-measure'/g, '"abs-measure"')
.replace(/'flex'/g, '"flex"')
.replace(/'measure'/g, '"measure"')
.replace(/'stretch'/g, '"stretch"')
.replace(/undefined/g, 'null')
.replace(/CSS_UNDEFINED/g, 'CSSConstants.UNDEFINED')
.replace(/CSS_JUSTIFY_/g, 'CSSJustify.')
.replace(/CSS_MEASURE_MODE_/g, 'CSSMeasureMode.')
.replace(/CSS_ALIGN_/g, 'CSSAlign.')
.replace(/CSS_POSITION_/g, 'CSSPositionType.')
.replace(/CSS_OVERFLOW_/g, 'CSSOverflow.')
.replace(/css_flex_direction_t/g, 'CSSFlexDirection')
.replace(/css_direction_t/g, 'CSSDirection')
.replace(/css_align_t/g, 'CSSAlign')
@@ -21,6 +28,10 @@ function __transpileToCSharpCommon(code) {
.replace(/css_measure_mode_t/g, 'CSSMeasureMode')
.replace(/css_dim_t/g, 'MeasureOutput')
.replace(/bool/g, 'boolean')
.replace(/style\[CSS_LEFT/g, 'style.position[POSITION_LEFT')
.replace(/style\[CSS_TOP/g, 'style.position[POSITION_TOP')
.replace(/style\[CSS_RIGHT/g, 'style.position[POSITION_RIGHT')
.replace(/style\[CSS_BOTTOM/g, 'style.position[POSITION_BOTTOM')
.replace(/style\[dim/g, 'style.dimensions[dim')
.replace(/(style|layout)\.width/g, '$1.dimensions[DIMENSION_WIDTH]')
.replace(/(style|layout)\.height/g, '$1.dimensions[DIMENSION_HEIGHT]')
@@ -28,22 +39,24 @@ function __transpileToCSharpCommon(code) {
.replace(/layout\[pos/g, 'layout.position[pos')
.replace(/layout\[leading/g, 'layout.position[leading')
.replace(/layout\[trailing/g, 'layout.position[trailing')
.replace(/layout\[measuredDim/g, 'layout.measuredDimensions[dim')
.replace(/layout\.measuredWidth/g, 'layout.measuredDimensions[DIMENSION_WIDTH]')
.replace(/layout\.measuredHeight/g, 'layout.measuredDimensions[DIMENSION_HEIGHT]')
.replace(/getPositionType\((.+?)\)/g, '$1.style.positionType')
.replace(/getJustifyContent\((.+?)\)/g, '$1.style.justifyContent')
.replace(/getAlignContent\((.+?)\)/g, '$1.style.alignContent')
.replace(/isPosDefined\((.+?),\s*(.+?)\)/g, '!isUndefined\($1.style.position[$2]\)')
.replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.style.dimensions[dim[$2]]\) && $1.style.dimensions[dim[$2]] >= 0.0\)')
.replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.layout.dimensions[dim[$2]]\) && $1.layout.dimensions[dim[$2]] >= 0.0\)')
.replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '($1.style.dimensions[dim[$2]] >= 0.0)')
.replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '($1.layout.measuredDimensions[dim[$2]] >= 0.0)')
.replace(/getPosition\((.+?),\s*(.+?)\)/g, '\(isUndefined\($1.style.position[$2]\) ? 0 : $1.style.position[$2]\)')
.replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.dimensions[dim[$3]] - $2.layout.dimensions[dim[$3]] - $2.layout.position[pos[$3]]')
.replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex > 0\)')
.replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.measuredDimensions[dim[$3]] - ($2.style.positionType == CSSPositionType.Absolute ? 0 : $2.layout.measuredDimensions[dim[$3]]) - $2.layout.position[pos[$3]]')
.replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex != 0\)')
.replace(/isFlexWrap\((.+?)\)/g, '\($1.style.flexWrap == CSSWrap.WRAP\)')
.replace(/getPaddingAndBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingPaddingAndBorder($1, $2) + getTrailingPaddingAndBorder($1, $2)\)')
.replace(/getBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingBorder($1, $2) + getTrailingBorder($1, $2)\)')
.replace(/getMarginAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)')
.replace(/getLeadingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getLeadingPadding($1, $2) + getLeadingBorder($1, $2)\)')
.replace(/getTrailingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getTrailingPadding($1, $2) + getTrailingBorder($1, $2)\)')
.replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.dimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)')
.replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.measuredDimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)')
.replace(/getLeadingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(leadingSpacing[$2], leading[$2])')
.replace(/getTrailingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(trailingSpacing[$2], trailing[$2])')
.replace(/getLeadingPadding\((.+?),\s*(.+?)\)/g, '$1.style.padding.getWithFallback(leadingSpacing[$2], leading[$2])')
@@ -51,14 +64,18 @@ function __transpileToCSharpCommon(code) {
.replace(/getLeadingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(leadingSpacing[$2], leading[$2])')
.replace(/getTrailingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(trailingSpacing[$2], trailing[$2])')
.replace(/isRowDirection\((.+?)\)/g, '\($1 == CSS_FLEX_DIRECTION_ROW || $1 == CSS_FLEX_DIRECTION_ROW_REVERSE\)')
.replace(/assert\((.+?),\s*'(.+?)'\)/g, 'Assertions.assertCondition($1, "$2")')
.replace(/isUndefined\((.+?)\)/g, 'float.IsNaN\($1\)')
.replace(/getOverflow\((.+?)\)/g, '$1.style.overflow')
.replace(/layoutNodeInternal\((.+?)\)/g, 'layoutNodeInternal(layoutContext, $1)')
.replace(/style\.position\[CSS_/g, 'style.position[POSITION_')
.replace(/\/\*\(c\)!([^*]+)\*\//g, '')
.replace(/var\/\*\(java\)!([^*]+)\*\//g, '$1')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '$1')
// additional case conversions
.replace(/(CSSConstants|CSSWrap|CSSJustify|CSSMeasureMode|CSSAlign|CSSPositionType)\.([_A-Z]+)/g,
.replace(/(CSSConstants|CSSWrap|CSSJustify|CSSMeasureMode|CSSAlign|CSSPositionType|CSSOverflow)\.([_A-Z]+)/g,
function(str, match1, match2) {
return match1 + '.' + constantToPascalCase(match2);
});
@@ -139,12 +156,12 @@ var CSharpTranspiler = {
transpileLayoutEngine: function(code) {
return indent(
__transpileToCSharpCommon(code)
.replace(/function\s+layoutNode.*/, '')
.replace('node.style.measure', 'node.measure')
.replace(/\.children\.length/g, '.getChildCount()')
.replace(/node.children\[i\]/g, 'node.getChildAt(i)')
.replace(/node.children\[ii\]/g, 'node.getChildAt(ii)')
.replace(/node.children\[j\]/g, 'node.getChildAt(j)')
.replace(/fmaxf/g, 'Math.Max')
.replace(/fminf/g, 'Math.Min')
.replace(/\/\*\([^\/]+\*\/\n/g, '') // remove comments for other languages
.replace(/var\/\*([^\/]+)\*\//g, '$1')
.replace(/ === /g, ' == ')

View File

@@ -9,11 +9,18 @@
function __transpileToJavaCommon(code) {
return code
.replace(/'abs-layout'/g, '"abs-layout"')
.replace(/'abs-measure'/g, '"abs-measure"')
.replace(/'flex'/g, '"flex"')
.replace(/'measure'/g, '"measure"')
.replace(/'stretch'/g, '"stretch"')
.replace(/undefined/g, 'null')
.replace(/CSS_UNDEFINED/g, 'CSSConstants.UNDEFINED')
.replace(/CSS_JUSTIFY_/g, 'CSSJustify.')
.replace(/CSS_MEASURE_MODE_/g, 'CSSMeasureMode.')
.replace(/CSS_ALIGN_/g, 'CSSAlign.')
.replace(/CSS_POSITION_/g, 'CSSPositionType.')
.replace(/CSS_OVERFLOW_/g, 'CSSOverflow.')
.replace(/css_flex_direction_t/g, 'CSSFlexDirection')
.replace(/css_direction_t/g, 'CSSDirection')
.replace(/css_align_t/g, 'CSSAlign')
@@ -21,6 +28,10 @@ function __transpileToJavaCommon(code) {
.replace(/css_measure_mode_t/g, 'CSSMeasureMode')
.replace(/css_dim_t/g, 'MeasureOutput')
.replace(/bool/g, 'boolean')
.replace(/style\[CSS_LEFT/g, 'style.position[POSITION_LEFT')
.replace(/style\[CSS_TOP/g, 'style.position[POSITION_TOP')
.replace(/style\[CSS_RIGHT/g, 'style.position[POSITION_RIGHT')
.replace(/style\[CSS_BOTTOM/g, 'style.position[POSITION_BOTTOM')
.replace(/style\[dim/g, 'style.dimensions[dim')
.replace(/(style|layout)\.width/g, '$1.dimensions[DIMENSION_WIDTH]')
.replace(/(style|layout)\.height/g, '$1.dimensions[DIMENSION_HEIGHT]')
@@ -28,22 +39,24 @@ function __transpileToJavaCommon(code) {
.replace(/layout\[pos/g, 'layout.position[pos')
.replace(/layout\[leading/g, 'layout.position[leading')
.replace(/layout\[trailing/g, 'layout.position[trailing')
.replace(/layout\[measuredDim/g, 'layout.measuredDimensions[dim')
.replace(/layout\.measuredWidth/g, 'layout.measuredDimensions[DIMENSION_WIDTH]')
.replace(/layout\.measuredHeight/g, 'layout.measuredDimensions[DIMENSION_HEIGHT]')
.replace(/getPositionType\((.+?)\)/g, '$1.style.positionType')
.replace(/getJustifyContent\((.+?)\)/g, '$1.style.justifyContent')
.replace(/getAlignContent\((.+?)\)/g, '$1.style.alignContent')
.replace(/isPosDefined\((.+?),\s*(.+?)\)/g, '!isUndefined\($1.style.position[$2]\)')
.replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.style.dimensions[dim[$2]]\) && $1.style.dimensions[dim[$2]] >= 0.0\)')
.replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.layout.dimensions[dim[$2]]\) && $1.layout.dimensions[dim[$2]] >= 0.0\)')
.replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '($1.style.dimensions[dim[$2]] >= 0.0)')
.replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '($1.layout.measuredDimensions[dim[$2]] >= 0.0)')
.replace(/getPosition\((.+?),\s*(.+?)\)/g, '\(isUndefined\($1.style.position[$2]\) ? 0 : $1.style.position[$2]\)')
.replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.dimensions[dim[$3]] - $2.layout.dimensions[dim[$3]] - $2.layout.position[pos[$3]]')
.replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex > 0\)')
.replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.measuredDimensions[dim[$3]] - ($2.style.positionType == CSSPositionType.ABSOLUTE ? 0 : $2.layout.measuredDimensions[dim[$3]]) - $2.layout.position[pos[$3]]')
.replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex != 0\)')
.replace(/isFlexWrap\((.+?)\)/g, '\($1.style.flexWrap == CSSWrap.WRAP\)')
.replace(/getPaddingAndBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingPaddingAndBorder($1, $2) + getTrailingPaddingAndBorder($1, $2)\)')
.replace(/getBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingBorder($1, $2) + getTrailingBorder($1, $2)\)')
.replace(/getMarginAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)')
.replace(/getLeadingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getLeadingPadding($1, $2) + getLeadingBorder($1, $2)\)')
.replace(/getTrailingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getTrailingPadding($1, $2) + getTrailingBorder($1, $2)\)')
.replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.dimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)')
.replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.measuredDimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)')
.replace(/getLeadingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(leadingSpacing[$2], leading[$2])')
.replace(/getTrailingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(trailingSpacing[$2], trailing[$2])')
.replace(/getLeadingPadding\((.+?),\s*(.+?)\)/g, '$1.style.padding.getWithFallback(leadingSpacing[$2], leading[$2])')
@@ -51,7 +64,11 @@ function __transpileToJavaCommon(code) {
.replace(/getLeadingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(leadingSpacing[$2], leading[$2])')
.replace(/getTrailingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(trailingSpacing[$2], trailing[$2])')
.replace(/isRowDirection\((.+?)\)/g, '\($1 == CSS_FLEX_DIRECTION_ROW || $1 == CSS_FLEX_DIRECTION_ROW_REVERSE\)')
.replace(/assert\((.+?),\s*'(.+?)'\)/g, 'Assertions.assertCondition($1, "$2")')
.replace(/isUndefined\((.+?)\)/g, 'Float.isNaN\($1\)')
.replace(/getOverflow\((.+?)\)/g, '$1.style.overflow')
.replace(/layoutNodeInternal\((.+?)\)/g, 'layoutNodeInternal(layoutContext, $1)')
.replace(/style\.position\[CSS_/g, 'style.position[POSITION_')
.replace(/\/\*\(c\)!([^*]+)\*\//g, '')
.replace(/var\/\*\(java\)!([^*]+)\*\//g, '$1')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '$1');
@@ -118,12 +135,12 @@ var JavaTranspiler = {
transpileLayoutEngine: function(code) {
return indent(
__transpileToJavaCommon(code)
.replace(/function\s+layoutNode.*/, '')
.replace('node.style.measure', 'node.measure')
.replace(/\.children\.length/g, '.getChildCount()')
.replace(/node.children\[i\]/g, 'node.getChildAt(i)')
.replace(/node.children\[ii\]/g, 'node.getChildAt(ii)')
.replace(/node.children\[j\]/g, 'node.getChildAt(j)')
.replace(/fmaxf/g, 'Math.max')
.replace(/fminf/g, 'Math.min')
.replace(/\/\*\([^\/]+\*\/\n/g, '') // remove comments for other languages
.replace(/var\/\*([^\/]+)\*\//g, '$1')
.replace(/ === /g, ' == ')

View File

@@ -92,6 +92,7 @@ var layoutTestUtils = (function() {
margin: 0;
padding: 0;
min-width: 0;
}
hack to ignore three hundred px width of the body {}
@@ -115,20 +116,28 @@ var layoutTestUtils = (function() {
if (typeof computeLayout === 'object') {
var fillNodes = computeLayout.fillNodes;
var realComputeLayout = computeLayout.computeLayout;
var canUseCachedMeasurement = computeLayout.canUseCachedMeasurement;
}
function extractNodes(node) {
var layout = node.layout;
delete node.layout;
var keysToCopy = [
'width',
'height',
'left',
'top'
];
var layout = {};
keysToCopy.forEach(function(key) {
layout[key] = node.layout[key];
});
if (node.children && node.children.length > 0) {
layout.children = node.children.map(extractNodes);
} else {
delete node.children;
}
delete layout.right;
delete layout.bottom;
delete layout.direction;
delete node.layout;
return layout;
}
@@ -183,13 +192,17 @@ var layoutTestUtils = (function() {
function computeDOMLayout(node) {
var body = getIframe().contentDocument.body;
function setStyle(div, name, value) {
div.style['-webkit-' + name] = value;
div.style['webkit' + capitalizeFirst(name)] = value;
div.style[name] = value;
}
function transfer(div, node, name, ext) {
if (name in node.style) {
var value = node.style[name] + (ext || '');
div.style['-webkit-' + name] = value;
div.style['webkit' + capitalizeFirst(name)] = value;
div.style[name] = value;
setStyle(div, name, value);
}
}
@@ -202,7 +215,19 @@ var layoutTestUtils = (function() {
transfer(div, node, type + 'Start' + suffix, 'px');
transfer(div, node, type + 'End' + suffix, 'px');
}
function transferFlex(div, node) {
if ('flex' in node.style) {
var flex = node.style.flex;
var resolvedFlex = (
flex < 0 ? '0 1 auto' :
flex > 0 ? (flex + ' 0 0') :
'0 0 auto'
);
setStyle(div, 'flex', resolvedFlex);
}
}
function renderNode(parent, node) {
var div = document.createElement('div');
transfer(div, node, 'width', 'px');
@@ -220,13 +245,14 @@ var layoutTestUtils = (function() {
transferSpacing(div, node, 'border', 'Width');
transfer(div, node, 'flexDirection');
transfer(div, node, 'direction');
transfer(div, node, 'flex');
transferFlex(div, node);
transfer(div, node, 'flexWrap');
transfer(div, node, 'justifyContent');
transfer(div, node, 'alignSelf');
transfer(div, node, 'alignItems');
transfer(div, node, 'alignContent');
transfer(div, node, 'position');
transfer(div, node, 'overflow');
parent.appendChild(div);
(node.children || []).forEach(function(child) {
renderNode(div, child);
@@ -299,6 +325,22 @@ var layoutTestUtils = (function() {
function testExtractNodes(node, extractedNode) {
expect(extractNodes(node)).toEqual(extractedNode);
}
function testCanUseCachedMeasurement(canReuse, spec, cacheEntry) {
var availableWidth = spec.availableWidth;
var availableHeight = spec.availableHeight;
var widthMeasureMode = spec.widthMeasureMode;
var heightMeasureMode = spec.heightMeasureMode;
expect(
canUseCachedMeasurement(
availableWidth, availableHeight,
0, 0,
widthMeasureMode, heightMeasureMode,
cacheEntry
)
).toEqual(canReuse);
}
function testNamedLayout(name, layoutA, layoutB) {
expect(nameLayout(name, layoutA))
@@ -479,6 +521,7 @@ var layoutTestUtils = (function() {
},
testFillNodes: testFillNodes,
testExtractNodes: testExtractNodes,
testCanUseCachedMeasurement: testCanUseCachedMeasurement,
testRandomLayout: function(node) {
var layout = computeCSSLayout(node);
var domLayout = computeDOMLayout(node);

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,11 @@ typedef enum {
CSS_JUSTIFY_SPACE_AROUND
} css_justify_t;
typedef enum {
CSS_OVERFLOW_VISIBLE = 0,
CSS_OVERFLOW_HIDDEN
} css_overflow_t;
// Note: auto is only a valid value for alignSelf. It is NOT a valid value for
// alignItems.
typedef enum {
@@ -79,7 +84,8 @@ typedef enum {
typedef enum {
CSS_MEASURE_MODE_UNDEFINED = 0,
CSS_MEASURE_MODE_EXACTLY,
CSS_MEASURE_MODE_AT_MOST
CSS_MEASURE_MODE_AT_MOST,
CSS_MEASURE_MODE_COUNT
} css_measure_mode_t;
typedef enum {
@@ -87,20 +93,40 @@ typedef enum {
CSS_HEIGHT
} css_dimension_t;
typedef struct {
float available_width;
float available_height;
css_measure_mode_t width_measure_mode;
css_measure_mode_t height_measure_mode;
float computed_width;
float computed_height;
} css_cached_measurement_t;
enum {
// This value was chosen based on empiracle data. Even the most complicated
// layouts should not require more than 16 entries to fit within the cache.
CSS_MAX_CACHED_RESULT_COUNT = 16
};
typedef struct {
float position[4];
float dimensions[2];
css_direction_t direction;
float flex_basis;
// Instead of recomputing the entire layout every single time, we
// cache some information to break early when nothing changed
bool should_update;
float last_requested_dimensions[2];
float last_parent_max_width;
float last_parent_max_height;
float last_dimensions[2];
float last_position[2];
css_direction_t last_direction;
int generation_count;
css_direction_t last_parent_direction;
int next_cached_measurements_index;
css_cached_measurement_t cached_measurements[CSS_MAX_CACHED_RESULT_COUNT];
float measured_dimensions[2];
css_cached_measurement_t cached_layout;
} css_layout_t;
typedef struct {
@@ -116,6 +142,7 @@ typedef struct {
css_align_t align_self;
css_position_type_t position_type;
css_wrap_type_t flex_wrap;
css_overflow_t overflow;
float flex;
float margin[6];
float position[4];
@@ -143,8 +170,7 @@ struct css_node {
int children_count;
int line_index;
css_node_t *next_absolute_child;
css_node_t *next_flex_child;
css_node_t* next_child;
css_dim_t (*measure)(void *context, float width, css_measure_mode_t widthMode, float height, css_measure_mode_t heightMode);
void (*print)(void *context);
@@ -166,12 +192,8 @@ typedef enum {
} css_print_options_t;
void print_css_node(css_node_t *node, css_print_options_t options);
// Function that computes the layout!
void layoutNode(css_node_t *node, float availableWidth, float availableHeight, css_direction_t parentDirection);
bool isUndefined(float value);
// Function that computes the layout!
void layoutNode(css_node_t *node, float maxWidth, float maxHeight, css_direction_t parentDirection);
// Reset the calculated layout values for a given node. You should call this before `layoutNode`.
void resetNodeLayout(css_node_t *node);
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ var testLayout = layoutTestUtils.testLayout;
var testLayoutAgainstDomOnly = layoutTestUtils.testLayoutAgainstDomOnly;
var testLayoutAgainstExpectedOnly = layoutTestUtils.testLayoutAgainstExpectedOnly;
var testFillNodes = layoutTestUtils.testFillNodes;
var testCanUseCachedMeasurement = layoutTestUtils.testCanUseCachedMeasurement;
var text = layoutTestUtils.text;
var texts = layoutTestUtils.texts;
var textSizes = layoutTestUtils.textSizes;
@@ -38,6 +39,152 @@ describe('Javascript Only', function() {
{layout: {width: 200}},
{layout: {width: 200}, style: {}, children: []}
);
});
it('should only invoke measure function one time in simple layout', function() {
var measureInvocations = 0;
function measure(width, widthMode, height, heightMode) {
measureInvocations++;
return { width: 25, height: 25 };
}
testLayoutAgainstExpectedOnly(
{style: {width: 100, height: 100}, children: [
{style: {measure: measure}}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 25, top: 0, left: 0}
]}
);
expect(measureInvocations).toEqual(1);
});
});
describe('JavaScript Only: canUseCachedTextMeasurement', function() {
var measureModes = ['undefined', 'exactly', 'at-most'];
var assertCanReuse = testCanUseCachedMeasurement.bind(null, true);
var assertCannotReuse = testCanUseCachedMeasurement.bind(null, false);
it('should not reuse when width mode is "exactly" and available width != measurement', function() {
measureModes.forEach(function(widthMeasureMode) {
measureModes.forEach(function(heightMeasureMode) {
var computedWidth = 100;
var computedHeight = 200;
var availableWidth = widthMeasureMode === 'undefined' ? undefined : computedWidth;
var availableHeight = heightMeasureMode === 'undefined' ? undefined : computedHeight;
var cacheEntry = {
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: computedWidth,
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: computedHeight
};
assertCannotReuse(
{
availableWidth: computedWidth - 1, widthMeasureMode: 'exactly',
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
},
cacheEntry
);
assertCannotReuse(
{
availableWidth: computedWidth + 1, widthMeasureMode: 'exactly',
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
},
cacheEntry
);
});
});
});
it('should not reuse when height mode is "exactly" and available height != measurement', function() {
measureModes.forEach(function(widthMeasureMode) {
measureModes.forEach(function(heightMeasureMode) {
var computedWidth = 100;
var computedHeight = 200;
var availableWidth = widthMeasureMode === 'undefined' ? undefined : computedWidth;
var availableHeight = heightMeasureMode === 'undefined' ? undefined : computedHeight;
var cacheEntry = {
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: computedWidth,
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: computedHeight
};
assertCannotReuse(
{
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
availableHeight: computedHeight - 1, heightMeasureMode: 'exactly'
},
cacheEntry
);
assertCannotReuse(
{
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
availableHeight: computedHeight + 1, heightMeasureMode: 'exactly'
},
cacheEntry
);
});
});
});
it('should reuse exact matches', function() {
measureModes.forEach(function(widthMeasureMode) {
measureModes.forEach(function(heightMeasureMode) {
var availableWidth = widthMeasureMode === 'undefined' ? undefined : 100;
var availableHeight = heightMeasureMode === 'undefined' ? undefined : 200;
assertCanReuse(
{
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
},
{
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: 1,
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: 2
}
);
});
});
});
it('should reuse cache entry with unconstrained width when width mode is "exactly" and available width == measurement', function() {
measureModes.forEach(function(heightMeasureMode) {
var computedWidth = 100;
var computedHeight = 200;
var availableHeight = heightMeasureMode === 'undefined' ? undefined : computedHeight;
assertCanReuse(
{
availableWidth: computedWidth, widthMeasureMode: 'exactly',
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
},
{
availableWidth: undefined, widthMeasureMode: 'undefined', computedWidth: computedWidth,
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: computedHeight
}
);
});
});
it('should reuse cache entry with unconstrained height when height mode is "exactly" and height == measurement', function() {
measureModes.forEach(function(widthMeasureMode) {
var computedWidth = 100;
var computedHeight = 200;
var availableWidth = widthMeasureMode === 'undefined' ? undefined : computedWidth;
assertCanReuse(
{
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
availableHeight: computedHeight, heightMeasureMode: 'exactly'
},
{
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: computedWidth,
availableHeight: undefined, heightMeasureMode: 'undefined', computedHeight: computedHeight
}
);
});
});
});
@@ -1238,7 +1385,7 @@ describe('Layout', function() {
testLayoutAgainstExpectedOnly(
{style: {width: 320, flexDirection: 'column'}, children: [
{style: {measure: measureWithRatio2}},
{style: {height: 100, flexDirection: 'row'}, children: [
{style: {height: 100, flexDirection: 'row', overflow: 'hidden'}, children: [
{style: {measure: measureWithRatio2}},
{style: {measure: measureWithRatio2}}
]},
@@ -1354,14 +1501,16 @@ describe('Layout', function() {
});
it('should layout node with text bounded by grand-parent', function() {
testLayout(
testLayoutAgainstExpectedOnly(
{style: {width: 100, padding: 10, alignSelf: 'flex-start'}, children: [
{style: {margin: 10, alignSelf: 'flex-start'}, children: [
{style: {measure: text(texts.big)}}
]}
]},
{width: 100, height: 40 + textSizes.bigHeight, top: 0, left: 0, children: [
{width: textSizes.bigMinWidth, height: textSizes.bigHeight, top: 20, left: 20, children: [
// In the flexbox engine implementation, min width of text is not supported so we max
// out at the amount of available space (60)
{width: Math.min(60, textSizes.bigMinWidth), height: textSizes.bigHeight, top: 20, left: 20, children: [
{width: textSizes.bigMinWidth, height: textSizes.bigHeight, top: 0, left: 0}
]}
]}
@@ -2149,6 +2298,30 @@ describe('Layout', function() {
]}
);
});
it('should center flexible item with max size', function() {
testLayout(
{style: {width: 1000, height: 1000, flexDirection: 'row', justifyContent: 'center'}, children: [
{style: {flex: 1, maxWidth: 600, height: 1000}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 600, height: 1000, top: 0, left: 200}
]}
);
});
it('should correctly size flexible items with flex basis and a max width', function() {
testLayout(
{style: {width: 1000, height: 1000, flexDirection: 'row'}, children: [
{style: {flex: 1, width: 100, height: 1000}},
{style: {flex: 1, width: 100, maxWidth: 200, height: 1000}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 800, height: 1000, top: 0, left: 0},
{width: 200, height: 1000, top: 0, left: 800}
]}
);
});
xit('should layout node with a nested sibling child with width', function() {
testLayout(
@@ -2485,6 +2658,307 @@ describe('Layout', function() {
});
});
describe('Layout flex:-1', function() {
// Tests for items with flex:-1 in a container with flexDirection:column
it('should not shrink column node when there is space left over', function() {
testLayout(
{style: {width: 100, height: 100}, children: [
{style: {width: 100, flex: -1}, children: [
{style: {width: 100, height: 25}}
]}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 25, top: 0, left: 0, children: [
{width: 100, height: 25, top: 0, left: 0}
]}
]}
);
});
it('should shrink column node when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100}, children: [
{style: {width: 100, flex: -1}, children: [
{style: {width: 100, height: 200}}
]}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 200, top: 0, left: 0}
]}
]}
);
});
it('should not shrink column node with siblings when there is space left over', function() {
testLayout(
{style: {width: 100, height: 100}, children: [
{style: {width: 100, height: 25}},
{style: {width: 100, flex: -1}, children: [
{style: {width: 100, height: 30}}
]},
{style: {width: 100, height: 15}},
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 25, top: 0, left: 0},
{width: 100, height: 30, top: 25, left: 0, children: [
{width: 100, height: 30, top: 0, left: 0}
]},
{width: 100, height: 15, top: 55, left: 0},
]}
);
});
it('should shrink column node with siblings when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100}, children: [
{style: {width: 100, height: 25}},
{style: {width: 100, flex: -1}, children: [
{style: {width: 100, height: 80}}
]},
{style: {width: 100, height: 15}},
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 25, top: 0, left: 0},
{width: 100, height: 60, top: 25, left: 0, children: [
{width: 100, height: 80, top: 0, left: 0}
]},
{width: 100, height: 15, top: 85, left: 0},
]}
);
});
it('should shrink column nodes proportional to their main size when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100}, children: [
{style: {width: 100, height: 30, flex: -1}},
{style: {width: 100, height: 40}},
{style: {width: 100, height: 50, flex: -1}}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 22.5, top: 0, left: 0},
{width: 100, height: 40, top: 22.5, left: 0},
{width: 100, height: 37.5, top: 62.5, left: 0}
]}
);
});
// Tests for items with flex:-1 and overflow:visible in a container with flexDirection:row
it('should not shrink visible row node when there is space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {height: 100, flex: -1}, children: [
{style: {width: 25, height: 100}}
]}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0}
]}
]}
);
});
it('should shrink visible row node when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {height: 100, flex: -1}, children: [
{style: {width: 200, height: 100}}
]}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
// width would be 100 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 200, height: 100, top: 0, left: 0}
]}
]}
);
});
it('should not shrink visible row node with siblings when there is space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {width: 25, height: 100}},
{style: {height: 100, flex: -1}, children: [
{style: {width: 30, height: 100}}
]},
{style: {width: 15, height: 100}},
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0},
{width: 30, height: 100, top: 0, left: 25, children: [
{width: 30, height: 100, top: 0, left: 0}
]},
{width: 15, height: 100, top: 0, left: 55},
]}
);
});
it('should shrink visible row node with siblings when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {width: 25, height: 100}},
{style: {height: 100, flex: -1}, children: [
{style: {width: 80, height: 100}}
]},
{style: {width: 15, height: 100}},
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0},
// width would be 80 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0
{width: 60, height: 100, top: 0, left: 25, children: [
{width: 80, height: 100, top: 0, left: 0}
]},
{width: 15, height: 100, top: 0, left: 85},
]}
);
});
it('should shrink visible row nodes when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {width: 30, height: 100, flex: -1}},
{style: {width: 40, height: 100}},
{style: {width: 50, height: 100, flex: -1}}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
// width would be 30 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0
{width: 22.5, height: 100, top: 0, left: 0},
{width: 40, height: 100, top: 0, left: 22.5},
// width would be 50 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0
{width: 37.5, height: 100, top: 0, left: 62.5}
]}
);
});
// Tests for items with flex:-1 and overflow:hidden in a container with flexDirection:row
it('should not shrink hidden row node when there is space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {height: 100, flex: -1, overflow: 'hidden'}, children: [
{style: {width: 25, height: 100}}
]}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0}
]}
]}
);
});
it('should shrink hidden row node when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {height: 100, flex: -1, overflow: 'hidden'}, children: [
{style: {width: 200, height: 100}}
]}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 200, height: 100, top: 0, left: 0}
]}
]}
);
});
it('should not shrink hidden row node with siblings when there is space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {width: 25, height: 100}},
{style: {height: 100, flex: -1, overflow: 'hidden'}, children: [
{style: {width: 30, height: 100}}
]},
{style: {width: 15, height: 100}},
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0},
{width: 30, height: 100, top: 0, left: 25, children: [
{width: 30, height: 100, top: 0, left: 0}
]},
{width: 15, height: 100, top: 0, left: 55},
]}
);
});
it('should shrink hidden row node with siblings when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {width: 25, height: 100}},
{style: {height: 100, flex: -1, overflow: 'hidden'}, children: [
{style: {width: 80, height: 100}}
]},
{style: {width: 15, height: 100}},
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0},
{width: 60, height: 100, top: 0, left: 25, children: [
{width: 80, height: 100, top: 0, left: 0}
]},
{width: 15, height: 100, top: 0, left: 85},
]}
);
});
it('should shrink hidden row nodes proportional to their main size when there is not any space left over', function() {
testLayout(
{style: {width: 100, height: 100, flexDirection: 'row'}, children: [
{style: {width: 30, height: 100, flex: -1, overflow: 'hidden'}},
{style: {width: 40, height: 100}},
{style: {width: 50, height: 100, flex: -1, overflow: 'hidden'}}
]},
{width: 100, height: 100, top: 0, left: 0, children: [
{width: 22.5, height: 100, top: 0, left: 0},
{width: 40, height: 100, top: 0, left: 22.5},
{width: 37.5, height: 100, top: 0, left: 62.5}
]}
);
});
// Tests for items with flex:-1 containing a text node
it('should not shrink text node with siblings when there is space left over', function() {
testLayoutAgainstExpectedOnly(
{style: {width: 213, height: 100, flexDirection: 'row'}, children: [
{style: {width: 25, height: 100}},
{style: {height: 100, flex: -1, flexDirection: 'row', alignItems: 'flex-start'}, children: [
{style: {measure: text(texts.big)}}
]},
{style: {width: 15, height: 100}},
]},
{width: 213, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0},
{width: textSizes.bigWidth, height: 100, top: 0, left: 25, children: [
{width: textSizes.bigWidth, height: textSizes.smallHeight, top: 0, left: 0}
]},
{width: 15, height: 100, top: 0, left: 25 + textSizes.bigWidth},
]}
);
});
it('should shrink text node with siblings when there is not any space left over', function() {
testLayout(
{style: {width: 140, height: 100, flexDirection: 'row'}, children: [
{style: {width: 25, height: 100}},
{style: {height: 100, flex: -1, flexDirection: 'row', alignItems: 'flex-start'}, children: [
{style: {flex: -1, measure: text(texts.big)}}
]},
{style: {width: 15, height: 100}},
]},
{width: 140, height: 100, top: 0, left: 0, children: [
{width: 25, height: 100, top: 0, left: 0},
{width: textSizes.bigMinWidth, height: 100, top: 0, left: 25, children: [
{width: textSizes.bigMinWidth, height: textSizes.bigHeight, top: 0, left: 0}
]},
{width: 15, height: 100, top: 0, left: 25 + textSizes.bigMinWidth},
]}
);
});
});
describe('Layout alignContent', function() {
it('should layout with alignContent: stretch, and alignItems: flex-start', function() {

File diff suppressed because it is too large Load Diff

View File

@@ -18,5 +18,10 @@ namespace Facebook.CSSLayout
Debug.Assert(v != null);
return v;
}
public static void assertCondition(bool condition, string explanation)
{
Debug.Assert(condition, explanation);
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
namespace Facebook.CSSLayout
{
sealed class CSSCachedMeasurement
{
public float availableWidth;
public float availableHeight;
public CSSMeasureMode? widthMeasureMode = null;
public CSSMeasureMode? heightMeasureMode = null;
public float computedWidth;
public float computedHeight;
}
}

View File

@@ -16,6 +16,10 @@ namespace Facebook.CSSLayout
class CSSLayout
{
// This value was chosen based on empiracle data. Even the most complicated
// layouts should not require more than 16 entries to fit within the cache.
public const int MAX_CACHED_RESULT_COUNT = 16;
public const int POSITION_LEFT = 0;
public const int POSITION_TOP = 1;
public const int POSITION_RIGHT = 2;
@@ -25,12 +29,25 @@ namespace Facebook.CSSLayout
public const int DIMENSION_HEIGHT = 1;
public float[] position = new float[4];
public float[] dimensions = new float[2];
public float[] dimensions = {
CSSConstants.Undefined,
CSSConstants.Undefined
};
public CSSDirection direction = CSSDirection.LTR;
/**
* This should always get called before calling {@link LayoutEngine#layoutNode(CSSNode, float)}
*/
public float flexBasis;
public int generationCount;
public CSSDirection? lastParentDirection;
public int nextCachedMeasurementsIndex;
public CSSCachedMeasurement[] cachedMeasurements = new CSSCachedMeasurement[MAX_CACHED_RESULT_COUNT];
public float[] measuredDimensions = {
CSSConstants.Undefined,
CSSConstants.Undefined
};
public CSSCachedMeasurement cachedLayout = new CSSCachedMeasurement();
public void resetResult()
{
@@ -38,17 +55,18 @@ namespace Facebook.CSSLayout
FillArray(dimensions, CSSConstants.Undefined);
direction = CSSDirection.LTR;
}
public void copy(CSSLayout layout)
{
position[POSITION_LEFT] = layout.position[POSITION_LEFT];
position[POSITION_TOP] = layout.position[POSITION_TOP];
position[POSITION_RIGHT] = layout.position[POSITION_RIGHT];
position[POSITION_BOTTOM] = layout.position[POSITION_BOTTOM];
dimensions[DIMENSION_WIDTH] = layout.dimensions[DIMENSION_WIDTH];
dimensions[DIMENSION_HEIGHT] = layout.dimensions[DIMENSION_HEIGHT];
direction = layout.direction;
flexBasis = 0;
generationCount = 0;
lastParentDirection = null;
nextCachedMeasurementsIndex = 0;
measuredDimensions[DIMENSION_WIDTH] = CSSConstants.Undefined;
measuredDimensions[DIMENSION_HEIGHT] = CSSConstants.Undefined;
cachedLayout.widthMeasureMode = null;
cachedLayout.heightMeasureMode = null;
}
public override string ToString()

View File

@@ -22,5 +22,6 @@ namespace Facebook.CSSLayout
{
/*package*/
public MeasureOutput measureOutput = new MeasureOutput();
public int currentGenerationCount;
}
}

View File

@@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*

View File

@@ -58,8 +58,7 @@ namespace Facebook.CSSLayout
internal readonly CachedCSSLayout lastLayout = new CachedCSSLayout();
internal int lineIndex = 0;
internal /*package*/ CSSNode nextAbsoluteChild;
internal /*package*/ CSSNode nextFlexChild;
internal /*package*/ CSSNode nextChild;
// 4 is kinda arbitrary, but the default of 10 seems really high for an average View.
readonly List<CSSNode> mChildren = new List<CSSNode>(4);
@@ -155,7 +154,6 @@ namespace Facebook.CSSLayout
public void CalculateLayout()
{
layout.resetResult();
LayoutEngine.layoutNode(DummyLayoutContext, this, CSSConstants.Undefined, CSSConstants.Undefined, null);
}

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
namespace Facebook.CSSLayout
{
public enum CSSOverflow
{
Visible,
Hidden
}
}

View File

@@ -22,6 +22,7 @@ namespace Facebook.CSSLayout
public CSSAlign alignSelf = CSSAlign.Auto;
public CSSPositionType positionType = CSSPositionType.Relative;
public CSSWrap flexWrap = CSSWrap.NoWrap;
public CSSOverflow overflow = CSSOverflow.Visible;
public float flex;
public Spacing margin = new Spacing();

View File

@@ -41,14 +41,16 @@
<Compile Include="Assertions.cs" />
<Compile Include="CachedCSSLayout.cs" />
<Compile Include="CSSAlign.cs" />
<Compile Include="CSSCachedMeasurement.cs" />
<Compile Include="CSSConstants.cs" />
<Compile Include="CSSDirection.cs" />
<Compile Include="CSSFlexDirection.cs" />
<Compile Include="CSSJustify.cs" />
<Compile Include="CSSMeasureMode.cs" />
<Compile Include="CSSLayout.cs" />
<Compile Include="CSSLayoutContext.cs" />
<Compile Include="CSSMeasureMode.cs" />
<Compile Include="CSSNode.cs" />
<Compile Include="CSSOverflow.cs" />
<Compile Include="CSSPositionType.cs" />
<Compile Include="CSSStyle.cs" />
<Compile Include="CSSWrap.cs" />
@@ -68,4 +70,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.csslayout;
public class CSSCachedMeasurement {
public float availableWidth;
public float availableHeight;
public CSSMeasureMode widthMeasureMode = null;
public CSSMeasureMode heightMeasureMode = null;
public float computedWidth;
public float computedHeight;
}

View File

@@ -14,6 +14,10 @@ import java.util.Arrays;
* Where the output of {@link LayoutEngine#layoutNode(CSSNode, float)} will go in the CSSNode.
*/
public class CSSLayout {
// This value was chosen based on empiracle data. Even the most complicated
// layouts should not require more than 16 entries to fit within the cache.
public static final int MAX_CACHED_RESULT_COUNT = 16;
public static final int POSITION_LEFT = 0;
public static final int POSITION_TOP = 1;
public static final int POSITION_RIGHT = 2;
@@ -25,24 +29,38 @@ public class CSSLayout {
public float[] position = new float[4];
public float[] dimensions = new float[2];
public CSSDirection direction = CSSDirection.LTR;
/**
* This should always get called before calling {@link LayoutEngine#layoutNode(CSSNode, float)}
*/
public float flexBasis;
public int generationCount;
public CSSDirection lastParentDirection;
public int nextCachedMeasurementsIndex;
public CSSCachedMeasurement[] cachedMeasurements = new CSSCachedMeasurement[MAX_CACHED_RESULT_COUNT];
public float[] measuredDimensions = new float[2];
public CSSCachedMeasurement cachedLayout = new CSSCachedMeasurement();
CSSLayout() {
resetResult();
}
public void resetResult() {
Arrays.fill(position, 0);
Arrays.fill(dimensions, CSSConstants.UNDEFINED);
direction = CSSDirection.LTR;
}
public void copy(CSSLayout layout) {
position[POSITION_LEFT] = layout.position[POSITION_LEFT];
position[POSITION_TOP] = layout.position[POSITION_TOP];
position[POSITION_RIGHT] = layout.position[POSITION_RIGHT];
position[POSITION_BOTTOM] = layout.position[POSITION_BOTTOM];
dimensions[DIMENSION_WIDTH] = layout.dimensions[DIMENSION_WIDTH];
dimensions[DIMENSION_HEIGHT] = layout.dimensions[DIMENSION_HEIGHT];
direction = layout.direction;
flexBasis = 0;
generationCount = 0;
lastParentDirection = null;
nextCachedMeasurementsIndex = 0;
measuredDimensions[DIMENSION_WIDTH] = CSSConstants.UNDEFINED;
measuredDimensions[DIMENSION_HEIGHT] = CSSConstants.UNDEFINED;
cachedLayout.widthMeasureMode = null;
cachedLayout.heightMeasureMode = null;
}
@Override

View File

@@ -17,4 +17,5 @@ package com.facebook.csslayout;
*/
public class CSSLayoutContext {
/*package*/ final MeasureOutput measureOutput = new MeasureOutput();
int currentGenerationCount;
}

View File

@@ -63,9 +63,8 @@ public class CSSNode {
public int lineIndex = 0;
/*package*/ CSSNode nextAbsoluteChild;
/*package*/ CSSNode nextFlexChild;
/*package*/ CSSNode nextChild;
private @Nullable ArrayList<CSSNode> mChildren;
private @Nullable CSSNode mParent;
private @Nullable MeasureFunction mMeasureFunction = null;
@@ -139,7 +138,6 @@ public class CSSNode {
* Performs the actual layout and saves the results in {@link #layout}
*/
public void calculateLayout(CSSLayoutContext layoutContext) {
layout.resetResult();
LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, CSSConstants.UNDEFINED, null);
}

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.csslayout;
public enum CSSOverflow {
VISIBLE,
HIDDEN,
}

View File

@@ -23,6 +23,7 @@ public class CSSStyle {
public CSSAlign alignSelf;
public CSSPositionType positionType;
public CSSWrap flexWrap;
public CSSOverflow overflow;
public float flex;
public Spacing margin = new Spacing();
@@ -51,6 +52,7 @@ public class CSSStyle {
alignSelf = CSSAlign.AUTO;
positionType = CSSPositionType.RELATIVE;
flexWrap = CSSWrap.NOWRAP;
overflow = CSSOverflow.VISIBLE;
flex = 0f;
margin.reset();;

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ public class LayoutCachingTest {
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
root.calculateLayout(layoutContext);
assertTreeHasNewLayout(true, root);
markLayoutAppliedForTree(root);

File diff suppressed because it is too large Load Diff

View File

@@ -39,8 +39,7 @@ global.layoutTestUtils = {
};
global.describe = function(name, cb) {
if (name === 'Layout' ||
name === 'Layout alignContent') {
if (name.toLowerCase().indexOf('javascript only') === -1) {
cb();
}
};
@@ -175,6 +174,10 @@ function printLayout(test) {
'exactly': 'CSS_MEASURE_MODE_EXACTLY',
'at-most': 'CSS_MEASURE_MODE_AT_MOST'
});
addEnum(node, 'overflow', 'overflow', {
'visible': 'CSS_OVERFLOW_VISIBLE',
'hidden': 'CSS_OVERFLOW_HIDDEN'
});
addFloat(node, 'flex', 'flex');
addFloat(node, 'width', 'dimensions[CSS_WIDTH]');
addFloat(node, 'height', 'dimensions[CSS_HEIGHT]');
@@ -256,8 +259,13 @@ function printLayout(test) {
function transpileAnnotatedJStoC(jsCode) {
return jsCode
.replace(/'abs-layout'/g, '"abs-layout"')
.replace(/'abs-measure'/g, '"abs-measure"')
.replace(/'flex'/g, '"flex"')
.replace(/'measure'/g, '"measure"')
.replace(/'stretch'/g, '"stretch"')
.replace('node.style.measure', 'node.measure')
.replace(/null/g, 'NULL')
.replace(/undefined/g, 'NULL')
.replace(/\.children\.length/g, '.children_count')
.replace(/\.width/g, '.dimensions[CSS_WIDTH]')
.replace(/\.height/g, '.dimensions[CSS_HEIGHT]')
@@ -266,23 +274,30 @@ function transpileAnnotatedJStoC(jsCode) {
.replace(/\.minWidth/g, '.minDimensions[CSS_WIDTH]')
.replace(/\.minHeight/g, '.minDimensions[CSS_HEIGHT]')
.replace(/\.lineIndex/g, '.line_index')
.replace(/\.nextAbsoluteChild/g, '.next_absolute_child')
.replace(/\.nextFlexChild/g, '.next_flex_child')
.replace(/layout\[dim/g, 'layout.dimensions[dim')
.replace(/\.nextChild/g, '.next_child')
.replace(/\.flexBasis/g, '.flex_basis')
.replace(/layout\[pos/g, 'layout.position[pos')
.replace(/layout\[leading/g, 'layout.position[leading')
.replace(/layout\[trailing/g, 'layout.position[trailing')
.replace(/layout\[measuredDim/g, 'layout.measured_dimensions[dim')
.replace(/layout\.measuredWidth/g, 'layout.measured_dimensions[CSS_WIDTH]')
.replace(/layout\.measuredHeight/g, 'layout.measured_dimensions[CSS_HEIGHT]')
.replace(/style\[dim/g, 'style.dimensions[dim')
.replace(/style\[CSS_LEFT/g, 'style.position[CSS_LEFT')
.replace(/style\[CSS_TOP/g, 'style.position[CSS_TOP')
.replace(/style\[CSS_RIGHT/g, 'style.position[CSS_RIGHT')
.replace(/style\[CSS_BOTTOM/g, 'style.position[CSS_BOTTOM')
.replace(/node.children\[i\]/g, 'node->get_child(node->context, i)')
.replace(/node.children\[ii\]/g, 'node->get_child(node->context, ii)')
.replace(/node.children\[j\]/g, 'node->get_child(node->context, j)')
.replace(/node\./g, 'node->')
.replace(/child\./g, 'child->')
.replace(/parent\./g, 'parent->')
.replace(/currentAbsoluteChild\./g, 'currentAbsoluteChild->')
.replace(/currentFlexChild\./g, 'currentFlexChild->')
.replace(/currentRelativeChild\./g, 'currentRelativeChild->')
.replace(/getPositionType\((.+?)\)/g, '$1->style.position_type')
.replace(/getJustifyContent\((.+?)\)/g, '$1->style.justify_content')
.replace(/getAlignContent\((.+?)\)/g, '$1->style.align_content')
.replace(/assert\((.+?),\s*'(.+?)'\);/g, 'assert($1); // $2')
.replace(/getOverflow\((.+?)\)/g, '$1->style.overflow')
.replace(/var\/\*\(c\)!([^*]+)\*\//g, '$1')
.replace(/var\/\*([^\/]+)\*\//g, '$1')
.replace(/ === /g, ' == ')
@@ -290,8 +305,7 @@ function transpileAnnotatedJStoC(jsCode) {
.replace(/\n {2}/g, '\n')
.replace(/\/\*\(c\)!([^*]+)\*\//g, '$1')
.replace(/\/[*]!([^*]+)[*]\//g, '$1')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '')
.split('\n').slice(1, -1).join('\n');
.replace(/\/\*\(java\)!([^*]+)\*\//g, '');
}
function makeConstDefs() {
@@ -318,14 +332,18 @@ function generateFile(fileName, generatedContent) {
fs.writeFileSync(fileName, content);
}
// Extract the function body by trimming the first ('function layoutNode(...) {') and
// last ('}') lines. Also, start the function body with a blank line so that regexes
// that use \n to match the start of a line will match the actual first line.
var computeLayoutCode = [''].concat(computeLayout.toString().split('\n').slice(1, -1)).join('\n');
var allTestsInC = allTests.map(printLayout);
generateFile(__dirname + '/__tests__/Layout-test.c', allTestsInC.join('\n\n'));
generateFile(__dirname + '/Layout-test-utils.c', makeConstDefs());
generateFile(__dirname + '/Layout.c', transpileAnnotatedJStoC(computeLayout.toString()));
generateFile(__dirname + '/java/src/com/facebook/csslayout/LayoutEngine.java', JavaTranspiler.transpileLayoutEngine(computeLayout.toString()));
generateFile(__dirname + '/Layout.c', transpileAnnotatedJStoC(computeLayoutCode));
generateFile(__dirname + '/java/src/com/facebook/csslayout/LayoutEngine.java', JavaTranspiler.transpileLayoutEngine(computeLayoutCode));
generateFile(__dirname + '/java/tests/com/facebook/csslayout/TestConstants.java', JavaTranspiler.transpileCConstDefs(makeConstDefs()));
generateFile(__dirname + '/java/tests/com/facebook/csslayout/LayoutEngineTest.java', JavaTranspiler.transpileCTestsArray(allTestsInC));
generateFile(__dirname + '/csharp/Facebook.CSSLayout/LayoutEngine.cs', CSharpTranspiler.transpileLayoutEngine(computeLayout.toString()));
generateFile(__dirname + '/csharp/Facebook.CSSLayout/LayoutEngine.cs', CSharpTranspiler.transpileLayoutEngine(computeLayoutCode));
generateFile(__dirname + '/csharp/Facebook.CSSLayout.Tests/TestConstants.cs', CSharpTranspiler.transpileCConstDefs(makeConstDefs()));
generateFile(__dirname + '/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs', CSharpTranspiler.transpileCTestsArray(allTestsInC));