Compare commits

...

74 Commits

Author SHA1 Message Date
Colin Eberhardt
79d7291906 1.1.1 2015-11-12 14:32:34 +00:00
Colin Eberhardt
bae4eb1830 Updated distribution build 2015-11-12 14:31:58 +00:00
Colin Eberhardt
609d4ae69d Do not delete release instructions! 2015-11-12 14:26:15 +00:00
Colin Eberhardt
99c3a88df4 Update release process to include a build step!
See #149
2015-11-12 11:52:07 +00:00
Colin Eberhardt
4364c6ebb2 Merge pull request #150 from wpcarro/patch-1
grammatical fix
2015-11-11 07:41:42 +00:00
Colin Eberhardt
73b1e63bd7 1.1.0 2015-11-11 07:27:17 +00:00
William Carroll
b7856ce26a grammatical fix
also, on line 134, I could be wrong, but be wary of this sentence: "No C implementation of this function is provided in provided..." Perhaps this is a mistake.

thanks for all the awesome code!
2015-11-10 22:46:41 -08:00
Christopher Chedeau
f5eefe51f8 Merge pull request #148 from moneppo/master
Add C usage to documentation.
2015-11-07 08:39:01 -08:00
Michael
aac6694127 Add C usage to documentation.
Fixes #114.
2015-11-07 08:29:12 -08:00
Krzysztof Magiera
6e499300ff Merge pull request #146 from lucasr/style-getters
New style getters and cleanups in CSSNode
2015-10-21 16:42:46 +01:00
Lucas Rocha
d3b702e1ad Rename getStylePadding() to getPadding() for consistency
So that we're consistent with other style-only methods. Move existing
getters to be close to their matching setters in CSSNode.
2015-10-21 14:31:35 +01:00
Lucas Rocha
eb1d1726b9 Add getters for all style properties
For consistency and because now we have some use cases for it.
2015-10-21 10:37:09 +01:00
Christopher Chedeau
8f7632bc7f Merge pull request #144 from woehrl01/woehrl01-width
return correct value from Width property (C#)
2015-10-14 08:45:59 -07:00
Lukas Wöhrl
d2e66a8d82 return correct value from Width property 2015-10-14 08:50:49 +02:00
Lucas Rocha
4b4cd06be2 Merge pull request #140 from lucasr/fast-reset 2015-10-08 15:06:49 +01:00
Lucas Rocha
a821f6c555 Add reset() method to CSSNode
This allows users of css-layout in Java to perform faster resets on
CSSNode in cases when you want to recycle instances.
2015-10-08 14:49:28 +01:00
Colin Eberhardt
fbeef4542d Merge pull request #139 from ColinEberhardt/eslint
Eslint - enforces many more rules.
2015-10-07 22:26:04 +01:00
Colin Eberhardt
f02fbfc10c Changed from '"typeof foo === 'undefined'" to "foo === undefined" 2015-10-07 22:18:27 +01:00
Colin Eberhardt
e4c93e8c59 Updated to use eslint from fbjs-scripts 2015-10-07 21:52:22 +01:00
Krzysztof Magiera
35bd01e3f2 Merge pull request #141 from lucasr/enum-comparison
Faster enum comparisons in CSSNode
2015-10-07 13:19:56 +01:00
Colin Eberhardt
040f0f3e7c relaxed the eqeqeq rule to permit 'foo != null' 2015-10-07 06:40:17 +01:00
Lucas Rocha
af09213d1a Faster enum comparisons in CSSNode
Enums are singletons by design so it's safe (and faster) to compare by
reference. This also covers the null case.
2015-10-05 17:07:23 +01:00
Colin Eberhardt
538cb2e940 Fixed build
(I was using an older verson of eslint locally)
2015-10-05 13:30:59 +01:00
Colin Eberhardt
45f62c424c Widened scope of CI eslint to all JavaScript files 2015-10-05 13:21:19 +01:00
Colin Eberhardt
c33e255182 Enforced quote style and indentation 2015-10-05 13:18:27 +01:00
Colin Eberhardt
0f43977bb2 Enforced the eqeqeq rule 2015-10-05 12:50:54 +01:00
Colin Eberhardt
9b75493988 auto-configured eslint and fixed gruntfile linting failures 2015-10-05 08:08:57 +01:00
Christopher Chedeau
0faaabb78c Merge pull request #138 from devongovett/js-caching
Implement caching in the JS version
2015-10-04 16:07:45 -07:00
Devon Govett
e510c72111 Update based on feedback 2015-10-04 14:36:44 -07:00
Devon Govett
57d41f3e35 Add a shouldUpdate property to nodes whose layout changed 2015-10-04 14:19:02 -07:00
Devon Govett
e9d880a105 Add isDirty support 2015-10-04 14:11:55 -07:00
Devon Govett
221510cfcf Fix nits 2015-10-04 14:11:07 -07:00
Devon Govett
2636a4fbed Merge branch 'master' of github.com:facebook/css-layout into js-caching 2015-10-04 12:33:17 -07:00
Devon Govett
7b2140d7f9 Implement caching in the JS version 2015-10-04 11:45:54 -07:00
Christopher Chedeau
3042bac0bb Merge pull request #137 from devongovett/js-optimization
JS optimization
2015-10-04 07:24:43 -07:00
Devon Govett
5af85c5ef6 Inline some isUndefined calls 2015-10-03 23:14:04 -07:00
Devon Govett
0f5d3ae8f0 Don't use the in operator 2015-10-03 23:13:33 -07:00
Devon Govett
39b45c65c1 Speed up margin/padding/border lookups 2015-10-03 23:11:09 -07:00
Christopher Chedeau
948241b659 Merge pull request #136 from getsetbro/patch-1
Update README.md
2015-10-02 15:51:43 -07:00
Seth Broweleit
2cf795c118 Update README.md
"alignContent" is not supported - see image: https://raw.githubusercontent.com/getsetbro/images/gh-pages/screenshots/reactnative.no.flexbox.aligncontent.png
2015-10-02 16:36:29 -05:00
Christopher Chedeau
9eb00949ae Merge pull request #135 from pragmatrix/csharp5-pr
Don't use C#6 language features for the time being
2015-09-30 09:21:05 -07:00
Armin Sander
2e908bfdee don't use C#6 language features for the time being 2015-09-30 07:57:59 +02:00
Christopher Chedeau
cefd6ccb96 Merge pull request #134 from vjeux/fix_dim_0
Fix width being ignored when has a value of 0
2015-09-25 14:43:52 -07:00
Christopher Chedeau
e280a577ae Fix width being ignored when has a value of 0
8f6a96adbc added a test in isDimDefined that checks if `value > 0.0`, but unfortunately, it did not faithfully port the JavaScript version which is `value >= 0.0`. Sadly, no test covered this so it went unnoticed.
2015-09-25 13:10:21 -07:00
Christopher Chedeau
246005cc84 Update dist/ to be the latest version 2015-09-24 11:56:18 -07:00
Christopher Chedeau
4ca2ea3466 Merge pull request #129 from pragmatrix/csharp-fb-pr
C# Transpiler, API, and Tests
2015-09-23 09:08:15 -07:00
Armin Sander
4de0721a24 C# transpiler, API, and tests 2015-09-23 09:27:45 +02:00
Lucas Rocha
4ef24028be Merge pull request #126 from AaaChiuuu/master 2015-09-16 08:20:50 +01:00
Aaron Chiu
a353a11bf4 Merge pull request #1 from AaaChiuuu/publicConstants
Make CSSLayout constants public
2015-09-15 11:43:10 +01:00
Aaron Chiu
4a7936aa24 Make CSSLayout constants public 2015-09-15 11:42:30 +01:00
Aaron Chiu
f51c2d004d Merge pull request #123 from lucasr/omg-faster-flexbox
Even faster flexbox
2015-09-15 04:37:43 +01:00
Lucas Rocha
e43a8b28d6 Add 'package' comment in CSSNode for consistency 2015-09-14 18:23:36 +01:00
Lucas Rocha
cf94d35b51 Implement cascasing checks via bitwise flags
It turns the spacing resolution in Java is fairly expensive right now
because it doesn't a bunch of unnecessary checks in the array,
especially when the Spacing instance doesn't have any values set on it.

This diff changes Spacing to store the state of the defined values in a
bitwise flag so that padding/border/margin queries are a lot faster
during layout calculations. This gives us as extra 20% performance win
in my local benchmarks on Android
2015-09-14 18:23:36 +01:00
Lucas Rocha
ebc56fee59 Inline private methods at build time in Java
Unfortunately, Java doesn't have any build-time inlining solution and
method invocations do have a big performance impact on Android. This
changes Java's transpiler to inline almost all internal methods at build
time. This gives us a 30% performance win in my local benchmarks.

There's a drawback from moving code to the transpiler but I think this
is worth it (given the massive perf wins here) and the inlined methods
are fairly simple.
2015-09-14 18:23:36 +01:00
Christopher Chedeau
2120285467 Merge pull request #122 from lucasr/even-faster-flexbox
Even faster flexbox
2015-09-10 14:25:41 -07:00
Lucas Rocha
765ff8463e Add fast path for simple stack layouts
Change the initial line loop to opportunistically position children in
the in container with simple stacking params i.e. vertical/horizontal
stacking on non-flexible STRETCH/FLEX_START aligned. This allows us to
skip the main and cross axis loops (Loop C and D, respectively)
partially and even completely in many common scenarios.

In my benchamrks, this gives us about ~15% performance win in many
setups.
2015-09-10 11:19:28 +01:00
Lucas Rocha
2d869489ef More efficient resetResult() loop in LayoutEngine 2015-09-10 11:19:28 +01:00
Lucas Rocha
909c14117f Fix dimension check for STRETCH children
The correct check is on the layout state, no the style.
2015-09-10 11:19:28 +01:00
Lucas Rocha
9a149c83ff Avoid extra work when justifyContent is FLEX_START
Remaining space and between space only need to be updated when
justifyContent is not FLEX_START.
2015-09-10 11:19:28 +01:00
Lucas Rocha
2321165d53 Move condition to the non-redundant block
No need to check for RELATIVE position when position type is ABSOLUTE
and dimension is set.
2015-09-08 15:34:51 +01:00
Lucas Rocha
d1a49a4f0b Reduce search range of flexible children
We were traversing all children to only perform calculations/changes to
flexible children in order to avoid new allocations during layout. This
diff ensures we only visit flexible children during layout calculations
if any are present. We accomplish this by keeping a private linked list
of flexible children.
2015-09-08 15:34:51 +01:00
Lucas Rocha
793220faf8 Skip final loop on absolute children, if possible
There's no need to go through all absolute children at the end of the
layout calculation if the node at hand doesn't have any. This also
ensures only absolutely positioned children are traversed in the final
loop.
2015-09-08 15:34:51 +01:00
Lucas Rocha
996f2a03d5 Merge pre-fill loop into main line loop
There's no need to go through all children before starting the main line loop
as we'll visit all children in the former loop anyway. This diff merges the
pre-fill loop into the main line one to avoid an extraneous traversal on the
node's children.
2015-09-08 15:34:27 +01:00
Lucas Rocha
877a2838a6 Skip trailing position loop, if possible
There's no need to set the trailing position on left-to-right layouts
as the nodes will already have what we need (x, y, width, and height).
This means we still have an extra cost for reversed layout directions
but they are not as common as LTR ones.
2015-09-08 15:33:26 +01:00
Lucas Rocha
1ab785b7a3 Inline immutable values in layout algorithm
Store immutable values from the node being laid out to avoid unnecessary
method invocations during layout calculation. This gives us a 3%-5%
performance boost in my benchmarks on Android.
2015-09-08 15:15:52 +01:00
Lucas Rocha
06c708053f Change Java to use array indexes instead of methods
Method invocations are not entirely free on Android. Change the
generated Java code to use the same array-based approach used in
JS and C to compute dimensions, positions, etc instead of relying
too heavily on method invovations. As a bonus, the Java transpiler
becomes a lot simpler because the code is more analogous to the C
counterpart.

In my local benchmarks this change gives us a major performance
boost on Android (between 15% and 30%) depending on the device
and the runtime (Dalvik|Art).
2015-09-08 15:15:46 +01:00
Colin Eberhardt
486b9a84bf 1.0.0 2015-09-08 07:45:08 +01:00
Colin Eberhardt
00c8428015 Merge pull request #119 from ColinEberhardt/keywords
Keywords
2015-09-01 06:51:59 +01:00
Colin Eberhardt
ecb8f8e610 Added layout keyword 2015-09-01 06:10:34 +01:00
Colin Eberhardt
baf34484fe Updated repo to include cdnjs / npm instructions 2015-08-31 22:15:46 +01:00
Colin Eberhardt
5dd1482148 Minor build change
removed duplication of the step that creates the 'dist' folder
2015-08-31 22:09:30 +01:00
Colin Eberhardt
9d1abd8d51 added keywords 2015-08-31 22:05:14 +01:00
Christopher Chedeau
e6d3aea73d Merge pull request #117 from paramaggarwal/watch
Watch for changes and run tests during development using `grunt watch`.
2015-08-28 09:49:46 -07:00
Param Aggarwal
f9d308f923 Watch for changes and run tests during development using grunt watch. 2015-08-28 22:15:57 +05:30
60 changed files with 16118 additions and 4246 deletions

View File

@@ -1,6 +1,5 @@
{
"browser": true,
"shadow": true,
"extends": "./node_modules/fbjs-scripts/eslint/.eslintrc",
"globals": {
"jasmine": true,
"describe": true,
@@ -17,8 +16,5 @@
"console": true,
"setTimeout": true,
"define": true
},
"rules": {
"quotes": [2, "single"]
}
}

View File

@@ -1,9 +1,8 @@
'use strict';
module.exports = function(grunt) {
var fs = require('fs');
var path = require('path');
var isWindows = /^win/.test(process.platform);
var isWindows = (/^win/).test(process.platform);
require('load-grunt-tasks')(grunt);
@@ -25,9 +24,8 @@ module.exports = function(grunt) {
config.cTestOutput = 'c_test.exe';
config.cTestCompile = 'cl -nologo -Zi -Tpsrc/__tests__/Layout-test.c -Tpsrc/Layout.c -Tpsrc/Layout-test-utils.c -link -incremental:no -out:"<%= config.cTestOutput %>"';
config.cTestExecute = '<%= config.cTestOutput %>';
config.cTestClean = ['<%= config.cTestOutput %>','*.obj','*.pdb'];
}
else {
config.cTestClean = ['<%= config.cTestOutput %>', '*.obj', '*.pdb'];
} else {
// GCC build (OSX, Linux, ...), assumes gcc is in the path.
config.cTestOutput = 'c_test';
config.cTestCompile = 'gcc -std=c99 -Werror -Wno-padded src/__tests__/Layout-test.c src/Layout.c src/Layout-test-utils.c -lm -o "./<%= config.cTestOutput %>"';
@@ -42,12 +40,12 @@ module.exports = function(grunt) {
dist: {
options: {
create: ['<%= config.distFolder %>']
},
},
}
}
},
clean: {
dist: ['<%= config.distFolder %>'],
dist: ['<%= config.distFolder %>/css-layout.*'],
cTest: config.cTestClean,
javaTest: ['**/*.class']
},
@@ -56,12 +54,12 @@ module.exports = function(grunt) {
options: {
configFile: '.eslintrc'
},
target: ['<%= config.srcFolder %>/Layout.js']
target: ['<%= config.srcFolder %>/**/*.js', './Gruntfile.js']
},
includereplace: {
options: {
prefix: '// @@',
prefix: '// @@'
},
main: {
src: '<%= config.srcFolder %>/<%= config.libName %>.js',
@@ -120,17 +118,16 @@ module.exports = function(grunt) {
'#ifdef CSS_LAYOUT_IMPLEMENTATION',
src,
'#endif // CSS_LAYOUT_IMPLEMENTATION'
].join('\n')
}
else {
].join('\n');
} else {
return src;
}
},
}
},
dist: {
src: ['<%= config.srcFolder %>/Layout.h', '<%= config.srcFolder %>/Layout.c'],
dest: '<%= config.distFolder %>/css-layout.h',
},
dest: '<%= config.distFolder %>/css-layout.h'
}
},
shell: {
@@ -149,6 +146,11 @@ module.exports = function(grunt) {
javaPackage: {
command: 'jar cf <%= config.distFolder %>/<%= config.libName %>.jar <%= config.javaSource %>'
}
},
watch: {
files: ['src/Layout.js'],
tasks: ['ci']
}
});
@@ -165,20 +167,23 @@ module.exports = function(grunt) {
grunt.registerTask('test-javascript', ['eslint', 'karma']);
// Packages the JavaScript as a single UMD module and minifies
grunt.registerTask('package-javascript', ['mkdir:dist', 'includereplace', 'uglify']);
grunt.registerTask('package-javascript', ['includereplace', 'uglify']);
// Packages the Java as a JAR
grunt.registerTask('package-java', ['mkdir:dist', 'shell:javaPackage']);
grunt.registerTask('package-java', ['shell:javaPackage']);
// Packages the C code as a single header
grunt.registerTask('package-c', ['mkdir:dist', 'concat']);
grunt.registerTask('package-c', ['concat']);
// package all languages
grunt.registerTask('package-all', ['package-javascript', 'package-java', 'package-c']);
// Default build, performs the full works!
grunt.registerTask('build', ['test-javascript', 'transpile', 'clean:dist', 'package-javascript', 'package-java', 'package-c']);
grunt.registerTask('build', ['test-javascript', 'transpile', 'clean:dist', 'mkdir:dist', 'package-all']);
// The JavaScript unit tests require Chrome (they need a faithful flexbox implementation
// to test against), so under CI this step is skipped.
grunt.registerTask('ci', ['eslint', 'transpile', 'clean:dist', 'package-javascript', 'package-java', 'package-c']);
grunt.registerTask('ci', ['eslint', 'transpile', 'clean:dist', 'mkdir:dist', 'package-all']);
grunt.registerTask('default', ['build']);
};

View File

@@ -3,9 +3,11 @@ css-layout [![Build Status](https://travis-ci.org/facebook/css-layout.svg?branch
This project implements a subset of CSS including flexbox and the box model using pure JavaScript, then transpiled to C and Java. The goal is to have a small standalone library to layout elements. It doesn't rely on the DOM at all.
The Java, C and JavaScript version of the code is available via [npm](https://www.npmjs.com/package/css-layout) or directly from the `dist` folder of this repo. The JavaScript version is also available via [cdnjs](https://cdnjs.com/libraries/css-layout).
In order to make sure that the code is correct, it is developed in JavaScript using TDD where each commit adds a unit test and the associated code to make it work. All the unit tests are tested against Chrome's implementation of CSS.
The JavaScript version has been implemented in a way that can be easily transpiled to C and Java via regexes. The layout function doesn't do any allocation nor uses any of the dynamic aspect of JavaScript. The tests are also transpiled to make sure that the implementations are correct everywhere.
The JavaScript version has been implemented in a way that can be easily transpiled to C and Java via regexes. The layout function doesn't do any allocation nor uses any of the dynamic aspects of JavaScript. The tests are also transpiled to make sure that the implementations are correct everywhere.
Usage
@@ -89,7 +91,7 @@ padding, paddingLeft, paddingRight, paddingTop, paddingBottom | positive number
borderWidth, borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth | positive number
flexDirection | 'column', 'row'
justifyContent | 'flex-start', 'center', 'flex-end', 'space-between', 'space-around'
alignItems, alignSelf, alignContent | 'flex-start', 'center', 'flex-end', 'stretch'
alignItems, alignSelf | 'flex-start', 'center', 'flex-end', 'stretch'
flex | positive number
flexWrap | 'wrap', 'nowrap'
position | 'relative', 'absolute'
@@ -124,6 +126,13 @@ div, span {
- All the flex elements are oriented from top to bottom, left to right and do not shrink. This is how things are laid out using the default CSS settings and what you'd expect.
- Everything is `position: relative`. This makes `position: absolute` target the direct parent and not some parent which is either `relative` or `absolute`. If you want to position an element relative to something else, you should move it in the DOM instead of relying of CSS. It also makes `top, left, right, bottom` do something when not specifying `position: absolute`.
Native Usage Notes
------------------
The C equivalent of `computeLayout` is [`layoutNode`](dist/css-layout.h#L1378).
In order for layout to properly layout reflowable text, the `measure` function must be set on the `css_node` structure. The property can be found in [`css-layout.h`](dist/css-layout.h#L146). This function must take a void pointer to a `context` that will affect the size of the node and the `width` as computed by the layout engine, and must return a `css_dim_t` structure defining the actual needed size of the node. For the most part, the `context` field can be the text inside the node. No C implementation of this function is provided in provided - it depends on your use of css-layout. However an implementation of the function in JavaScript can be used for reference in the [test utilities](src/Layout-test-utils.js#L383).
Development
-----------

5
dist/README.md vendored
View File

@@ -6,6 +6,9 @@ Releases can be found on [npm](https://www.npmjs.com/package/css-layout).
# Ensure that the local codebase is up to date
git fetch upstream master && git checkout FETCH_HEAD
# build the latest version of the library
grunt
# increment the version number and tag the release
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease]
@@ -14,4 +17,4 @@ git push --tags upstream HEAD:master
# publish this new version to npm
npm publish
```
```

492
dist/css-layout.h vendored
View File

@@ -1,7 +1,7 @@
/*
* #define CSS_LAYOUT_IMPLEMENTATION
* before you include this file in *one* C or C++ file to create the implementation.
*/
/*
* #define CSS_LAYOUT_IMPLEMENTATION
* before you include this file in *one* C or C++ file to create the implementation.
*/
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
@@ -133,18 +133,22 @@ typedef struct {
float maxDimensions[2];
} css_style_t;
typedef struct css_node {
typedef struct css_node css_node_t;
struct css_node {
css_style_t style;
css_layout_t layout;
int children_count;
int line_index;
css_node_t* next_absolute_child;
css_node_t* next_flex_child;
css_dim_t (*measure)(void *context, float width);
void (*print)(void *context);
struct css_node* (*get_child)(void *context, int i);
bool (*is_dirty)(void *context);
void *context;
} css_node_t;
};
// Lifecycle of nodes and children
@@ -165,7 +169,7 @@ void layoutNode(css_node_t *node, float maxWidth, css_direction_t parentDirectio
bool isUndefined(float value);
#endif
#ifdef CSS_LAYOUT_IMPLEMENTATION
/**
* Copyright (c) 2014, Facebook, Inc.
@@ -545,18 +549,6 @@ static float getPaddingAndBorderAxis(css_node_t *node, css_flex_direction_t axis
return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis);
}
static css_position_type_t getPositionType(css_node_t *node) {
return node->style.position_type;
}
static css_justify_t getJustifyContent(css_node_t *node) {
return node->style.justify_content;
}
static css_align_t getAlignContent(css_node_t *node) {
return node->style.align_content;
}
static css_align_t getAlignItem(css_node_t *node, css_node_t *child) {
if (child->style.align_self != CSS_ALIGN_AUTO) {
return child->style.align_self;
@@ -604,7 +596,7 @@ static float getFlex(css_node_t *node) {
static bool isFlex(css_node_t *node) {
return (
getPositionType(node) == CSS_POSITION_RELATIVE &&
node->style.position_type == CSS_POSITION_RELATIVE &&
getFlex(node) > 0
);
}
@@ -621,7 +613,7 @@ static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) {
static bool isDimDefined(css_node_t *node, css_flex_direction_t axis) {
float value = node->style.dimensions[dim[axis]];
return !isUndefined(value) && value > 0.0;
return !isUndefined(value) && value >= 0.0;
}
static bool isPosDefined(css_node_t *node, css_position_t position) {
@@ -722,23 +714,29 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
node->layout.position[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) +
getRelativePosition(node, crossAxis);
// Inline immutable values from the target node to avoid excessive method
// invocations during the layout calculation.
int childCount = node->children_count;
float paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis);
if (isMeasureDefined(node)) {
bool isResolvedRowDimDefined = !isUndefined(node->layout.dimensions[dim[resolvedRowAxis]]);
float width = CSS_UNDEFINED;
if (isDimDefined(node, resolvedRowAxis)) {
width = node->style.dimensions[CSS_WIDTH];
} else if (!isUndefined(node->layout.dimensions[dim[resolvedRowAxis]])) {
} else if (isResolvedRowDimDefined) {
width = node->layout.dimensions[dim[resolvedRowAxis]];
} else {
width = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis);
}
width -= getPaddingAndBorderAxis(node, resolvedRowAxis);
width -= paddingAndBorderAxisResolvedRow;
// We only need to give a dimension for the text if we haven't got any
// for it computed yet. It can either be from the style attribute or because
// the element is flexible.
bool isRowUndefined = !isDimDefined(node, resolvedRowAxis) &&
isUndefined(node->layout.dimensions[dim[resolvedRowAxis]]);
bool isRowUndefined = !isDimDefined(node, resolvedRowAxis) && !isResolvedRowDimDefined;
bool isColumnUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) &&
isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]);
@@ -751,66 +749,42 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
);
if (isRowUndefined) {
node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] +
getPaddingAndBorderAxis(node, resolvedRowAxis);
paddingAndBorderAxisResolvedRow;
}
if (isColumnUndefined) {
node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] +
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
}
}
if (node->children_count == 0) {
if (childCount == 0) {
return;
}
}
bool isNodeFlexWrap = isFlexWrap(node);
css_justify_t justifyContent = node->style.justify_content;
float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);
float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);
float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);
float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis);
bool isMainDimDefined = !isUndefined(node->layout.dimensions[dim[mainAxis]]);
bool isCrossDimDefined = !isUndefined(node->layout.dimensions[dim[crossAxis]]);
bool isMainRowDirection = isRowDirection(mainAxis);
int i;
int ii;
css_node_t* child;
css_flex_direction_t axis;
// Pre-fill some dimensions straight from the parent
for (i = 0; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (getAlignItem(node, child) == CSS_ALIGN_STRETCH &&
getPositionType(child) == CSS_POSITION_RELATIVE &&
!isUndefined(node->layout.dimensions[dim[crossAxis]]) &&
!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
} else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis])),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
}
}
css_node_t* firstAbsoluteChild = NULL;
css_node_t* currentAbsoluteChild = NULL;
float definedMainDim = CSS_UNDEFINED;
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
definedMainDim = node->layout.dimensions[dim[mainAxis]] -
getPaddingAndBorderAxis(node, mainAxis);
if (isMainDimDefined) {
definedMainDim = node->layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain;
}
// We want to execute the next two loops one per line with flex-wrap
@@ -822,7 +796,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float linesCrossDim = 0;
float linesMainDim = 0;
int linesCount = 0;
while (endLine < node->children_count) {
while (endLine < childCount) {
// <Loop A> Layout non flexible children and count children by type
// mainContentDim is accumulation of the dimensions and margin of all the
@@ -837,16 +811,99 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;
// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
bool isSimpleStackMain =
(isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) ||
(!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER);
int firstComplexMain = (isSimpleStackMain ? childCount : startLine);
// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
bool isSimpleStackCross = true;
int firstComplexCross = childCount;
css_node_t* firstFlexChild = NULL;
css_node_t* currentFlexChild = NULL;
float mainDim = leadingPaddingAndBorderMain;
float crossDim = 0;
float maxWidth;
for (i = startLine; i < node->children_count; ++i) {
for (i = startLine; i < childCount; ++i) {
child = node->get_child(node->context, i);
child->line_index = linesCount;
child->next_absolute_child = NULL;
child->next_flex_child = NULL;
css_align_t alignItem = getAlignItem(node, child);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (alignItem == CSS_ALIGN_STRETCH &&
child->style.position_type == CSS_POSITION_RELATIVE &&
isCrossDimDefined &&
!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] -
paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
} else if (child->style.position_type == CSS_POSITION_ABSOLUTE) {
// Store a private linked list of absolutely positioned children
// so that we can efficiently traverse them later.
if (firstAbsoluteChild == NULL) {
firstAbsoluteChild = child;
}
if (currentAbsoluteChild != NULL) {
currentAbsoluteChild->next_absolute_child = child;
}
currentAbsoluteChild = child;
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis])),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
}
float nextContentDim = 0;
// It only makes sense to consider a child flexible if we have a computed
// dimension for the node->
if (!isUndefined(node->layout.dimensions[dim[mainAxis]]) && isFlex(child)) {
if (isMainDimDefined && isFlex(child)) {
flexibleChildrenCount++;
totalFlexible += getFlex(child);
totalFlexible += child->style.flex;
// Store a private linked list of flexible children so that we can
// efficiently traverse them later.
if (firstFlexChild == NULL) {
firstFlexChild = child;
}
if (currentFlexChild != NULL) {
currentFlexChild->next_flex_child = child;
}
currentFlexChild = child;
// Even if we don't know its exact size yet, we already know the padding,
// border and margin. We'll use this partial information, which represents
@@ -857,14 +914,14 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
} else {
maxWidth = CSS_UNDEFINED;
if (!isRowDirection(mainAxis)) {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
getPaddingAndBorderAxis(node, resolvedRowAxis);
if (!isMainRowDirection) {
if (isDimDefined(node, resolvedRowAxis)) {
maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
getPaddingAndBorderAxis(node, resolvedRowAxis);
paddingAndBorderAxisResolvedRow;
} else {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
paddingAndBorderAxisResolvedRow;
}
}
@@ -875,7 +932,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// Absolute positioned elements do not take part of the layout, so we
// don't use them to compute mainContentDim
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
if (child->style.position_type == CSS_POSITION_RELATIVE) {
nonFlexibleChildrenCount++;
// At this point we know the final size and margin of the element.
nextContentDim = getDimWithMargin(child, mainAxis);
@@ -883,8 +940,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
}
// The element we are about to add would make us go to the next line
if (isFlexWrap(node) &&
!isUndefined(node->layout.dimensions[dim[mainAxis]]) &&
if (isNodeFlexWrap &&
isMainDimDefined &&
mainContentDim + nextContentDim > definedMainDim &&
// If there's only one element, then it's bigger than the content
// and needs its own line
@@ -893,6 +950,44 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
alreadyComputedNextLayout = 1;
break;
}
// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child-> The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(child->style.position_type != CSS_POSITION_RELATIVE || isFlex(child))) {
isSimpleStackMain = false;
firstComplexMain = i;
}
// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child-> The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(child->style.position_type != CSS_POSITION_RELATIVE ||
(alignItem != CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) ||
isUndefined(child->layout.dimensions[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}
if (isSimpleStackMain) {
child->layout.position[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}
mainDim += getDimWithMargin(child, mainAxis);
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}
if (isSimpleStackCross) {
child->layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}
alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
@@ -908,7 +1003,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// The remaining available space that needs to be allocated
float remainingMainDim = 0;
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
if (isMainDimDefined) {
remainingMainDim = definedMainDim - mainContentDim;
} else {
remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim;
@@ -921,21 +1016,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float baseMainDim;
float boundMainDim;
// Iterate over every child in the axis. If the flex share of remaining
// space doesn't meet min/max bounds, remove this child from flex
// calculations.
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (isFlex(child)) {
baseMainDim = flexibleMainDim * getFlex(child) +
getPaddingAndBorderAxis(child, mainAxis);
boundMainDim = boundAxis(child, mainAxis, baseMainDim);
// If the flex share of remaining space doesn't meet min/max bounds,
// remove this child from flex calculations.
currentFlexChild = firstFlexChild;
while (currentFlexChild != NULL) {
baseMainDim = flexibleMainDim * currentFlexChild->style.flex +
getPaddingAndBorderAxis(currentFlexChild, mainAxis);
boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim);
if (baseMainDim != boundMainDim) {
remainingMainDim -= boundMainDim;
totalFlexible -= getFlex(child);
}
if (baseMainDim != boundMainDim) {
remainingMainDim -= boundMainDim;
totalFlexible -= currentFlexChild->style.flex;
}
currentFlexChild = currentFlexChild->next_flex_child;
}
flexibleMainDim = remainingMainDim / totalFlexible;
@@ -944,37 +1038,37 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
if (flexibleMainDim < 0) {
flexibleMainDim = 0;
}
// We iterate over the full array and only apply the action on flexible
// children. This is faster than actually allocating a new array that
// contains only flexible children.
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (isFlex(child)) {
// At this point we know the final size of the element in the main
// dimension
child->layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis,
flexibleMainDim * getFlex(child) + getPaddingAndBorderAxis(child, mainAxis)
);
maxWidth = CSS_UNDEFINED;
if (isDimDefined(node, resolvedRowAxis)) {
maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
getPaddingAndBorderAxis(node, resolvedRowAxis);
} else if (!isRowDirection(mainAxis)) {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
getPaddingAndBorderAxis(node, resolvedRowAxis);
}
currentFlexChild = firstFlexChild;
while (currentFlexChild != NULL) {
// At this point we know the final size of the element in the main
// dimension
currentFlexChild->layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis,
flexibleMainDim * currentFlexChild->style.flex +
getPaddingAndBorderAxis(currentFlexChild, mainAxis)
);
// And we recursively call the layout algorithm for this child
layoutNode(child, maxWidth, direction);
maxWidth = CSS_UNDEFINED;
if (isDimDefined(node, resolvedRowAxis)) {
maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else if (!isMainRowDirection) {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
paddingAndBorderAxisResolvedRow;
}
// And we recursively call the layout algorithm for this child
layoutNode(currentFlexChild, maxWidth, direction);
child = currentFlexChild;
currentFlexChild = currentFlexChild->next_flex_child;
child->next_flex_child = NULL;
}
// We use justifyContent to figure out how to allocate the remaining
// space available
} else {
css_justify_t justifyContent = getJustifyContent(node);
} else if (justifyContent != CSS_JUSTIFY_FLEX_START) {
if (justifyContent == CSS_JUSTIFY_CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSS_JUSTIFY_FLEX_END) {
@@ -1001,15 +1095,12 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
float crossDim = 0;
float mainDim = leadingMainDim +
getLeadingPaddingAndBorder(node, mainAxis);
mainDim += leadingMainDim;
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexMain; i < endLine; ++i) {
child = node->get_child(node->context, i);
child->line_index = linesCount;
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
if (child->style.position_type == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
// In case the child is position absolute and has left/top being
// defined, we override the position to whatever the user said
@@ -1023,40 +1114,40 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
child->layout.position[pos[mainAxis]] += mainDim;
// Define the trailing position accordingly.
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}
}
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (child->style.position_type == CSS_POSITION_RELATIVE) {
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}
}
}
float containerCrossAxis = node->layout.dimensions[dim[crossAxis]];
if (isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (!isCrossDimDefined) {
containerCrossAxis = fmaxf(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, crossDim + getPaddingAndBorderAxis(node, crossAxis)),
getPaddingAndBorderAxis(node, crossAxis)
boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
}
// <Loop D> Position elements in the cross axis
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexCross; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
if (child->style.position_type == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[crossAxis])) {
// In case the child is absolutely positionned and has a
// top/left/bottom/right being set, we override all the previously
@@ -1066,20 +1157,22 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
getLeadingMargin(child, crossAxis);
} else {
float leadingCrossDim = getLeadingPaddingAndBorder(node, crossAxis);
float leadingCrossDim = leadingPaddingAndBorderCross;
// For a relative children, we're either using alignItems (parent) or
// alignSelf (child) in order to determine the position in the cross axis
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
if (child->style.position_type == CSS_POSITION_RELATIVE) {
/*eslint-disable */
// This variable is intentionally re-defined as the code is transpiled to a block scope language
css_align_t alignItem = getAlignItem(node, child);
/*eslint-enable */
if (alignItem == CSS_ALIGN_STRETCH) {
// You can only stretch if the dimension has not already been set
// previously.
if (!isDimDefined(child, crossAxis)) {
if (isUndefined(child->layout.dimensions[dim[crossAxis]])) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
boundAxis(child, crossAxis, containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis)),
paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
@@ -1088,8 +1181,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// The remaining space between the parent dimensions+padding and child
// dimensions+margin.
float remainingCrossDim = containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getDimWithMargin(child, crossAxis);
paddingAndBorderAxisCross - getDimWithMargin(child, crossAxis);
if (alignItem == CSS_ALIGN_CENTER) {
leadingCrossDim += remainingCrossDim / 2;
@@ -1103,7 +1195,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
child->layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim;
// Define the trailing position accordingly.
if (!isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}
@@ -1128,16 +1220,15 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm
// section 9.4
//
if (linesCount > 1 &&
!isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (linesCount > 1 && isCrossDimDefined) {
float nodeCrossAxisInnerSize = node->layout.dimensions[dim[crossAxis]] -
getPaddingAndBorderAxis(node, crossAxis);
paddingAndBorderAxisCross;
float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim;
float crossDimLead = 0;
float currentLead = getLeadingPaddingAndBorder(node, crossAxis);
float currentLead = leadingPaddingAndBorderCross;
css_align_t alignContent = getAlignContent(node);
css_align_t alignContent = node->style.align_content;
if (alignContent == CSS_ALIGN_FLEX_END) {
currentLead += remainingAlignContentDim;
} else if (alignContent == CSS_ALIGN_CENTER) {
@@ -1154,9 +1245,9 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// compute the line's height and find the endIndex
float lineHeight = 0;
for (ii = startIndex; ii < node->children_count; ++ii) {
for (ii = startIndex; ii < childCount; ++ii) {
child = node->get_child(node->context, ii);
if (getPositionType(child) != CSS_POSITION_RELATIVE) {
if (child->style.position_type != CSS_POSITION_RELATIVE) {
continue;
}
if (child->line_index != i) {
@@ -1174,7 +1265,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
for (ii = startIndex; ii < endIndex; ++ii) {
child = node->get_child(node->context, ii);
if (getPositionType(child) != CSS_POSITION_RELATIVE) {
if (child->style.position_type != CSS_POSITION_RELATIVE) {
continue;
}
@@ -1202,33 +1293,39 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// If the user didn't specify a width or height, and it has not been set
// by the container, then we set it via the children.
if (isUndefined(node->layout.dimensions[dim[mainAxis]])) {
if (!isMainDimDefined) {
node->layout.dimensions[dim[mainAxis]] = fmaxf(
// We're missing the last padding at this point to get the final
// dimension
boundAxis(node, mainAxis, linesMainDim + getTrailingPaddingAndBorder(node, mainAxis)),
// We can never assign a width smaller than the padding and borders
getPaddingAndBorderAxis(node, mainAxis)
paddingAndBorderAxisMain
);
needsMainTrailingPos = true;
if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsMainTrailingPos = true;
}
}
if (isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (!isCrossDimDefined) {
node->layout.dimensions[dim[crossAxis]] = fmaxf(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, linesCrossDim + getPaddingAndBorderAxis(node, crossAxis)),
getPaddingAndBorderAxis(node, crossAxis)
boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
needsCrossTrailingPos = true;
if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsCrossTrailingPos = true;
}
}
// <Loop F> Set trailing position if necessary
if (needsMainTrailingPos || needsCrossTrailingPos) {
for (i = 0; i < node->children_count; ++i) {
for (i = 0; i < childCount; ++i) {
child = node->get_child(node->context, i);
if (needsMainTrailingPos) {
@@ -1242,40 +1339,41 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
}
// <Loop G> Calculate dimensions for absolutely positioned elements
for (i = 0; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis])
),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
currentAbsoluteChild = firstAbsoluteChild;
while (currentAbsoluteChild != NULL) {
// Pre-fill dimensions when using absolute position and both offsets for
// the axis are defined (either both left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(currentAbsoluteChild, axis) &&
isPosDefined(currentAbsoluteChild, leading[axis]) &&
isPosDefined(currentAbsoluteChild, trailing[axis])) {
currentAbsoluteChild->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(currentAbsoluteChild, axis, node->layout.dimensions[dim[axis]] -
getBorderAxis(node, axis) -
getMarginAxis(currentAbsoluteChild, axis) -
getPosition(currentAbsoluteChild, leading[axis]) -
getPosition(currentAbsoluteChild, trailing[axis])
),
// You never want to go smaller than padding
getPaddingAndBorderAxis(currentAbsoluteChild, axis)
);
}
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (isPosDefined(child, trailing[axis]) &&
!isPosDefined(child, leading[axis])) {
child->layout.position[leading[axis]] =
node->layout.dimensions[dim[axis]] -
child->layout.dimensions[dim[axis]] -
getPosition(child, trailing[axis]);
}
if (isPosDefined(currentAbsoluteChild, trailing[axis]) &&
!isPosDefined(currentAbsoluteChild, leading[axis])) {
currentAbsoluteChild->layout.position[leading[axis]] =
node->layout.dimensions[dim[axis]] -
currentAbsoluteChild->layout.dimensions[dim[axis]] -
getPosition(currentAbsoluteChild, trailing[axis]);
}
}
child = currentAbsoluteChild;
currentAbsoluteChild = currentAbsoluteChild->next_absolute_child;
child->next_absolute_child = NULL;
}
/** END_GENERATED **/
}

BIN
dist/css-layout.jar vendored

Binary file not shown.

759
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

@@ -1,16 +1,21 @@
{
"name": "css-layout",
"version": "0.0.6",
"version": "1.1.1",
"description": "Reimplementation of CSS layout using pure JavaScript",
"main": "dist/css-layout.js",
"scripts": {
"test": "grunt ci",
"postversion": "git push --tags upstream HEAD:master"
"watch": "grunt watch",
"test": "grunt ci"
},
"repository": {
"type": "git",
"url": "https://github.com/facebook/css-layout.git"
},
"keywords": [
"css",
"flexbox",
"layout"
],
"author": "",
"license": "BSD",
"bugs": {
@@ -18,12 +23,15 @@
},
"homepage": "https://github.com/facebook/css-layout",
"devDependencies": {
"babel-eslint": "^4.1.3",
"fbjs-scripts": "^0.2.2",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-uglify": "^0.9.1",
"grunt-contrib-watch": "^0.6.1",
"grunt-eslint": "^17.1.0",
"grunt-execute": "^0.2.2",
"grunt-include-replace": "^3.1.0",

175
src/CSharpTranspiler.js Normal file
View File

@@ -0,0 +1,175 @@
/**
* 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.
*/
function __transpileToCSharpCommon(code) {
return code
.replace(/CSS_UNDEFINED/g, 'CSSConstants.UNDEFINED')
.replace(/CSS_JUSTIFY_/g, 'CSSJustify.')
.replace(/CSS_ALIGN_/g, 'CSSAlign.')
.replace(/CSS_POSITION_/g, 'CSSPositionType.')
.replace(/css_flex_direction_t/g, 'CSSFlexDirection')
.replace(/css_direction_t/g, 'CSSDirection')
.replace(/css_align_t/g, 'CSSAlign')
.replace(/css_justify_t/g, 'CSSJustify')
.replace(/css_dim_t/g, 'MeasureOutput')
.replace(/bool/g, 'boolean')
.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]')
.replace(/layout\[dim/g, 'layout.dimensions[dim')
.replace(/layout\[pos/g, 'layout.position[pos')
.replace(/layout\[leading/g, 'layout.position[leading')
.replace(/layout\[trailing/g, 'layout.position[trailing')
.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(/isDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.style.dimensions[dim[$2]]\) && $1.style.dimensions[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(/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(/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])')
.replace(/getTrailingPadding\((.+?),\s*(.+?)\)/g, '$1.style.padding.getWithFallback(trailingSpacing[$2], trailing[$2])')
.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(/isUndefined\((.+?)\)/g, 'float.IsNaN\($1\)')
.replace(/\/\*\(c\)!([^*]+)\*\//g, '')
.replace(/var\/\*\(java\)!([^*]+)\*\//g, '$1')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '$1')
// additional case conversions
.replace(/(CSSConstants|CSSWrap|CSSJustify|CSSAlign|CSSPositionType)\.([_A-Z]+)/g,
function(str, match1, match2) {
return match1 + '.' + constantToPascalCase(match2);
});
}
function __transpileSingleTestToCSharp(code) {
return __transpileToCSharpCommon(code)
.replace(/CSS_DIRECTION_/g, 'CSSDirection.')
.replace(/CSS_FLEX_DIRECTION_/g, 'CSSFlexDirection.')
.replace(/CSS_WRAP/g, 'CSSWrap.WRAP')
.replace(/new_test_css_node/g, 'new TestCSSNode')
.replace(// style.position[CSS_TOP] => style.position[CSSLayout.POSITION_TOP]
/(style|layout)\.position\[CSS_(LEFT|TOP|RIGHT|BOTTOM)\]/g,
function(str, match1, match2) {
return match1 + '.position[POSITION_' + match2 + ']';
})
.replace(// style.dimensions[CSS_WIDTH] => style.dimensions[CSSLayout.DIMENSION_WIDTH]
/(style|layout)\.dimensions\[CSS_(WIDTH|HEIGHT)\]/g,
function(str, match1, match2) {
return match1 + '.dimensions[DIMENSION_' + match2 + ']';
})
.replace(// style.maxDimensions[CSS_WIDTH] => style.maxWidth
/(style|layout)\.maxDimensions\[CSS_(WIDTH|HEIGHT)\]/g,
function(str, match1, match2) {
return match1 + '.max' + match2.substr(0, 1).toUpperCase() + match2.substr(1).toLowerCase();
})
.replace(// style.minDimensions[CSS_WIDTH] => style.minWidth
/(style|layout)\.minDimensions\[CSS_(WIDTH|HEIGHT)\]/g,
function(str, match1, match2) {
return match1 + '.min' + match2.substr(0, 1).toUpperCase() + match2.substr(1).toLowerCase();
})
.replace(// style.margin[CSS_TOP] = 12.3 => style.margin[Spacing.TOP].set(12.3)
/style\.(margin|border|padding)\[CSS_(TOP|BOTTOM|LEFT|RIGHT|START|END)\]\s+=\s+(-?[\.\d]+)/g,
function(str, match1, match2, match3) {
var propertyCap = match1.charAt(0).toUpperCase() + match1.slice(1);
return 'set' + propertyCap + '(Spacing.' + match2 + ', ' + match3 + ')';
})
.replace(// style.margin[CSS_TOP] => style.margin[Spacing.TOP]
/style\.(margin|border|padding)\[CSS_(TOP|BOTTOM|LEFT|RIGHT|START|END)\]/g,
function(str, match1, match2) {
return 'style.' + match1 + '.get(Spacing.' + match2 + ')';
})
.replace(/get_child\(.*context\,\s([^\)]+)\)/g, 'getChildAt($1)')
.replace(/init_css_node_children/g, 'addChildren')
.replace(/css_node_t(\s)\*/g, 'TestCSSNode$1')
.replace(/\->/g, '.')
.replace(/(\d+\.\d+)/g, '$1f')
.replace(// style.flex_direction => style.flexDirection
/style\.([^_\[\]\s]+)_(\w)(\w+)/g,
function(str, match1, match2, match3) {
return 'style.' + match1 + match2.toUpperCase() + match3;
})
.replace(/(\w+)\.measure\s+=\s+.+/, '$1.setMeasureFunction(sTestMeasureFunction);')
// additional case conversions
.replace(/(CSSWrap|CSSFlexDirection)\.([_A-Z]+)/g,
function(str, match1, match2) {
return match1 + '.' + constantToPascalCase(match2);
});
}
function indent(code) {
return code
.split('\n')
.map(function(line) { return ' ' + line; })
.join('\n');
}
function constantToPascalCase(str) {
return str[0] + str.substr(1)
.toLowerCase()
.replace(/_(.)/g,
function(_, m) { return m.toUpperCase(); });
}
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(/fmaxf/g, 'Math.Max')
.replace(/\/\*\([^\/]+\*\/\n/g, '') // remove comments for other languages
.replace(/var\/\*([^\/]+)\*\//g, '$1')
.replace(/ === /g, ' == ')
.replace(/ !== /g, ' != ')
.replace(/\n {2}/g, '\n')
.replace(/\/[*]!([^*]+)[*]\//g, '$1')
.replace(/css_node_t\*/g, 'CSSNode'));
},
transpileCConstDefs: function(cConstDefs) {
return indent(
cConstDefs
.replace(/#define\s+(\w+)\s+(\"[^\"]+\")/g, 'public static readonly string $1 = $2;')
.replace(/#define\s+(\w+)\s+(.+)/g, 'public static readonly float $1 = $2f;'));
},
transpileCTestsArray: function(allTestsInC) {
var allTestsInCSharp = [];
for (var i = 0; i < allTestsInC.length; i++) {
allTestsInCSharp[i] =
' [Test]\n' +
' public void TestCase' + i + '()\n' +
__transpileSingleTestToCSharp(allTestsInC[i]);
}
return allTestsInCSharp.join('\n\n');
}
};
if (typeof module !== 'undefined') {
module.exports = CSharpTranspiler;
}

View File

@@ -10,84 +10,85 @@
function __transpileToJavaCommon(code) {
return code
.replace(/CSS_UNDEFINED/g, 'CSSConstants.UNDEFINED')
.replace(/CSS_JUSTIFY_/g, 'CSSJustify.')
.replace(/CSS_ALIGN_/g, 'CSSAlign.')
.replace(/CSS_POSITION_/g, 'CSSPositionType.')
.replace(/css_flex_direction_t/g, 'CSSFlexDirection')
.replace(/css_direction_t/g, 'CSSDirection')
.replace(/CSS_DIRECTION_/g, 'CSSDirection.')
.replace(/CSS_FLEX_DIRECTION_/g, 'CSSFlexDirection.')
.replace(/css_align_t/g, 'CSSAlign')
.replace(/CSS_ALIGN_/g, 'CSSAlign.')
.replace(/CSS_WRAP/g, 'CSSWrap.WRAP')
.replace(/CSS_POSITION_/g, 'CSSPositionType.')
.replace(/css_justify_t/g, 'CSSJustify')
.replace(/CSS_JUSTIFY_/g, 'CSSJustify.')
.replace(/css_dim_t/g, 'MeasureOutput')
.replace(/bool/g, 'boolean')
.replace(/^(\s+)([^\s]+)\s+\+=/gm, '$1$2 = $2 +') // Expand +=
.replace(/leading\[([^\]]+)\]/g, 'getLeading($1)')
.replace(/trailing\[([^\]]+)\]/g, 'getTrailing($1)')
.replace(/pos\[([^\]]+)\]/g, 'getPos($1)')
.replace(/dim\[([^\]]+)\]/g, 'getDim($1)')
.replace(/isUndefined/g, 'CSSConstants.isUndefined')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '$1')
// Since Java doesn't store its attributes in arrays, we need to use setters/getters to access
// the appropriate layout/style fields
.replace(
/(\w+)\.layout\[((?:getLeading|getPos)\([^\)]+\))\]\s+=\s+([^;]+);/gm,
'setLayoutPosition($1, $2, $3);')
.replace(
/(\w+)\.layout\[((?:getTrailing|getPos)\([^\)]+\))\]\s+=\s+([^;]+);/gm,
'setLayoutPosition($1, $2, $3);')
.replace(
/(\w+)\.layout\.direction\s+=\s+([^;]+);/gm,
'setLayoutDirection($1, $2);')
.replace(/(\w+)\.layout\[((?:getLeading|getPos)\([^\]]+\))\]/g, 'getLayoutPosition($1, $2)')
.replace(/(\w+)\.layout\[((?:getTrailing|getPos)\([^\]]+\))\]/g, 'getLayoutPosition($1, $2)')
.replace(
/(\w+)\.layout\[(getDim\([^\)]+\))\]\s+=\s+([^;]+);/gm,
'setLayoutDimension($1, $2, $3);')
.replace(/(\w+)\.layout\[(getDim\([^\]]+\))\]/g, 'getLayoutDimension($1, $2)')
.replace(/(\w+)\.style\[((?:getLeading|getPos)\([^\]]+\))\]/g, 'getStylePosition($1, $2)')
.replace(/(\w+)\.style\[(getDim\([^\]]+\))\]/g, 'getStyleDimension($1, $2)');
.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]')
.replace(/layout\[dim/g, 'layout.dimensions[dim')
.replace(/layout\[pos/g, 'layout.position[pos')
.replace(/layout\[leading/g, 'layout.position[leading')
.replace(/layout\[trailing/g, 'layout.position[trailing')
.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(/isDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.style.dimensions[dim[$2]]\) && $1.style.dimensions[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(/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(/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])')
.replace(/getTrailingPadding\((.+?),\s*(.+?)\)/g, '$1.style.padding.getWithFallback(trailingSpacing[$2], trailing[$2])')
.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(/isUndefined\((.+?)\)/g, 'Float.isNaN\($1\)')
.replace(/\/\*\(c\)!([^*]+)\*\//g, '')
.replace(/var\/\*\(java\)!([^*]+)\*\//g, '$1')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '$1');
}
function __transpileSingleTestToJava(code) {
return __transpileToJavaCommon(code)
.replace(/CSS_DIRECTION_/g, 'CSSDirection.')
.replace(/CSS_FLEX_DIRECTION_/g, 'CSSFlexDirection.')
.replace(/CSS_WRAP/g, 'CSSWrap.WRAP')
.replace(/new_test_css_node/g, 'new TestCSSNode')
.replace( // style.dimensions[CSS_WIDTH] => style.width
.replace(// style.position[CSS_TOP] => style.position[CSSLayout.POSITION_TOP]
/(style|layout)\.position\[CSS_(LEFT|TOP|RIGHT|BOTTOM)\]/g,
function(str, match1, match2) {
return match1 + '.position[POSITION_' + match2 + ']';
})
.replace(// style.dimensions[CSS_WIDTH] => style.dimensions[CSSLayout.DIMENSION_WIDTH]
/(style|layout)\.dimensions\[CSS_(WIDTH|HEIGHT)\]/g,
function (str, match1, match2) {
return match1 + '.' + match2.toLowerCase();
function(str, match1, match2) {
return match1 + '.dimensions[DIMENSION_' + match2 + ']';
})
.replace( // style.maxDimensions[CSS_WIDTH] => style.maxWidth
.replace(// style.maxDimensions[CSS_WIDTH] => style.maxWidth
/(style|layout)\.maxDimensions\[CSS_(WIDTH|HEIGHT)\]/g,
function (str, match1, match2) {
return match1 + '.max' + match2.substr(0, 1).toUpperCase() + match2.substr(1).toLowerCase();
function(str, match1, match2) {
return match1 + '.max' + match2.substr(0, 1).toUpperCase() + match2.substr(1).toLowerCase();
})
.replace( // style.minDimensions[CSS_WIDTH] => style.minWidth
.replace(// style.minDimensions[CSS_WIDTH] => style.minWidth
/(style|layout)\.minDimensions\[CSS_(WIDTH|HEIGHT)\]/g,
function (str, match1, match2) {
return match1 + '.min' + match2.substr(0, 1).toUpperCase() + match2.substr(1).toLowerCase();
function(str, match1, match2) {
return match1 + '.min' + match2.substr(0, 1).toUpperCase() + match2.substr(1).toLowerCase();
})
.replace( // layout.position[CSS_TOP] => layout.y
/layout\.position\[CSS_(TOP|LEFT)\]/g,
function (str, match1) {
return 'layout.' + (match1 === 'TOP' ? 'top' : 'left');
})
.replace( // style.position[CSS_TOP] => style.positionTop
/style\.(position)\[CSS_(TOP|BOTTOM|LEFT|RIGHT)\]/g,
function (str, match1, match2) {
return 'style.' + match1 + match2[0] + match2.substring(1).toLowerCase();
})
.replace( // style.margin[CSS_TOP] = 12.3 => style.margin[Spacing.TOP].set(12.3)
.replace(// style.margin[CSS_TOP] = 12.3 => style.margin[Spacing.TOP].set(12.3)
/style\.(margin|border|padding)\[CSS_(TOP|BOTTOM|LEFT|RIGHT|START|END)\]\s+=\s+(-?[\.\d]+)/g,
function (str, match1, match2, match3) {
function(str, match1, match2, match3) {
var propertyCap = match1.charAt(0).toUpperCase() + match1.slice(1);
return 'set' + propertyCap + '(Spacing.' + match2 + ', ' + match3 + ')';
})
.replace( // style.margin[CSS_TOP] => style.margin[Spacing.TOP]
.replace(// style.margin[CSS_TOP] => style.margin[Spacing.TOP]
/style\.(margin|border|padding)\[CSS_(TOP|BOTTOM|LEFT|RIGHT|START|END)\]/g,
function (str, match1, match2) {
function(str, match1, match2) {
return 'style.' + match1 + '.get(Spacing.' + match2 + ')';
})
.replace(/get_child\(.*context\,\s([^\)]+)\)/g, 'getChildAt($1)')
@@ -95,10 +96,10 @@ function __transpileSingleTestToJava(code) {
.replace(/css_node_t(\s)\*/g, 'TestCSSNode$1')
.replace(/\->/g, '.')
.replace(/(\d+\.\d+)/g, '$1f')
.replace( // style.flex_direction => style.flexDirection
.replace(// style.flex_direction => style.flexDirection
/style\.([^_\[\]\s]+)_(\w)(\w+)/g,
function (str, match1, match2, match3) {
return 'style.' + match1 + match2.toUpperCase() + match3;
function(str, match1, match2, match3) {
return 'style.' + match1 + match2.toUpperCase() + match3;
})
.replace(/(\w+)\.measure\s+=\s+.+/, '$1.setMeasureFunction(sTestMeasureFunction);');
}

View File

@@ -23,13 +23,13 @@ var layoutTestUtils = (function() {
var testMeasurePrecision = 1.0;
if (typeof jasmine !== 'undefined') {
jasmine.matchersUtil.buildFailureMessage = function () {
var args = Array.prototype.slice.call(arguments, 0),
matcherName = args[0],
isNot = args[1],
actual = args[2],
expected = args.slice(3),
englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
jasmine.matchersUtil.buildFailureMessage = function() {
var args = Array.prototype.slice.call(arguments, 0);
var matcherName = args[0];
var isNot = args[1];
var actual = args[2];
var expected = args.slice(3);
var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
var pp = function(node) {
return jasmine.pp(node)
@@ -278,8 +278,7 @@ var layoutTestUtils = (function() {
var val = obj[key];
if (typeof val === 'number') {
obj[key] = Math.floor((val * testMeasurePrecision) + 0.5) / testMeasurePrecision;
}
else if (typeof val === 'object') {
} else if (typeof val === 'object') {
inplaceRoundNumbersInObject(val);
}
}
@@ -386,7 +385,7 @@ var layoutTestUtils = (function() {
document.body.appendChild(iframeText);
var body = iframeText.contentDocument.body;
if (width === undefined || width !== width) {
if (width === undefined || isNaN(width)) {
width = Infinity;
}
@@ -490,7 +489,7 @@ var layoutTestUtils = (function() {
reduceTest: reduceTest,
text: function(text) {
var fn = function(width) {
if (width === undefined || width !== width) {
if (width === undefined || isNaN(width)) {
width = Infinity;
}

View File

@@ -376,18 +376,6 @@ static float getPaddingAndBorderAxis(css_node_t *node, css_flex_direction_t axis
return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis);
}
static css_position_type_t getPositionType(css_node_t *node) {
return node->style.position_type;
}
static css_justify_t getJustifyContent(css_node_t *node) {
return node->style.justify_content;
}
static css_align_t getAlignContent(css_node_t *node) {
return node->style.align_content;
}
static css_align_t getAlignItem(css_node_t *node, css_node_t *child) {
if (child->style.align_self != CSS_ALIGN_AUTO) {
return child->style.align_self;
@@ -435,7 +423,7 @@ static float getFlex(css_node_t *node) {
static bool isFlex(css_node_t *node) {
return (
getPositionType(node) == CSS_POSITION_RELATIVE &&
node->style.position_type == CSS_POSITION_RELATIVE &&
getFlex(node) > 0
);
}
@@ -452,7 +440,7 @@ static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) {
static bool isDimDefined(css_node_t *node, css_flex_direction_t axis) {
float value = node->style.dimensions[dim[axis]];
return !isUndefined(value) && value > 0.0;
return !isUndefined(value) && value >= 0.0;
}
static bool isPosDefined(css_node_t *node, css_position_t position) {
@@ -553,23 +541,29 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
node->layout.position[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) +
getRelativePosition(node, crossAxis);
// Inline immutable values from the target node to avoid excessive method
// invocations during the layout calculation.
int childCount = node->children_count;
float paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis);
if (isMeasureDefined(node)) {
bool isResolvedRowDimDefined = !isUndefined(node->layout.dimensions[dim[resolvedRowAxis]]);
float width = CSS_UNDEFINED;
if (isDimDefined(node, resolvedRowAxis)) {
width = node->style.dimensions[CSS_WIDTH];
} else if (!isUndefined(node->layout.dimensions[dim[resolvedRowAxis]])) {
} else if (isResolvedRowDimDefined) {
width = node->layout.dimensions[dim[resolvedRowAxis]];
} else {
width = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis);
}
width -= getPaddingAndBorderAxis(node, resolvedRowAxis);
width -= paddingAndBorderAxisResolvedRow;
// We only need to give a dimension for the text if we haven't got any
// for it computed yet. It can either be from the style attribute or because
// the element is flexible.
bool isRowUndefined = !isDimDefined(node, resolvedRowAxis) &&
isUndefined(node->layout.dimensions[dim[resolvedRowAxis]]);
bool isRowUndefined = !isDimDefined(node, resolvedRowAxis) && !isResolvedRowDimDefined;
bool isColumnUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) &&
isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]);
@@ -582,66 +576,42 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
);
if (isRowUndefined) {
node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] +
getPaddingAndBorderAxis(node, resolvedRowAxis);
paddingAndBorderAxisResolvedRow;
}
if (isColumnUndefined) {
node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] +
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
}
}
if (node->children_count == 0) {
if (childCount == 0) {
return;
}
}
bool isNodeFlexWrap = isFlexWrap(node);
css_justify_t justifyContent = node->style.justify_content;
float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);
float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);
float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);
float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis);
bool isMainDimDefined = !isUndefined(node->layout.dimensions[dim[mainAxis]]);
bool isCrossDimDefined = !isUndefined(node->layout.dimensions[dim[crossAxis]]);
bool isMainRowDirection = isRowDirection(mainAxis);
int i;
int ii;
css_node_t* child;
css_flex_direction_t axis;
// Pre-fill some dimensions straight from the parent
for (i = 0; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (getAlignItem(node, child) == CSS_ALIGN_STRETCH &&
getPositionType(child) == CSS_POSITION_RELATIVE &&
!isUndefined(node->layout.dimensions[dim[crossAxis]]) &&
!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
} else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis])),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
}
}
css_node_t* firstAbsoluteChild = NULL;
css_node_t* currentAbsoluteChild = NULL;
float definedMainDim = CSS_UNDEFINED;
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
definedMainDim = node->layout.dimensions[dim[mainAxis]] -
getPaddingAndBorderAxis(node, mainAxis);
if (isMainDimDefined) {
definedMainDim = node->layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain;
}
// We want to execute the next two loops one per line with flex-wrap
@@ -653,7 +623,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float linesCrossDim = 0;
float linesMainDim = 0;
int linesCount = 0;
while (endLine < node->children_count) {
while (endLine < childCount) {
// <Loop A> Layout non flexible children and count children by type
// mainContentDim is accumulation of the dimensions and margin of all the
@@ -668,16 +638,99 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;
// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
bool isSimpleStackMain =
(isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) ||
(!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER);
int firstComplexMain = (isSimpleStackMain ? childCount : startLine);
// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
bool isSimpleStackCross = true;
int firstComplexCross = childCount;
css_node_t* firstFlexChild = NULL;
css_node_t* currentFlexChild = NULL;
float mainDim = leadingPaddingAndBorderMain;
float crossDim = 0;
float maxWidth;
for (i = startLine; i < node->children_count; ++i) {
for (i = startLine; i < childCount; ++i) {
child = node->get_child(node->context, i);
child->line_index = linesCount;
child->next_absolute_child = NULL;
child->next_flex_child = NULL;
css_align_t alignItem = getAlignItem(node, child);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (alignItem == CSS_ALIGN_STRETCH &&
child->style.position_type == CSS_POSITION_RELATIVE &&
isCrossDimDefined &&
!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] -
paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
} else if (child->style.position_type == CSS_POSITION_ABSOLUTE) {
// Store a private linked list of absolutely positioned children
// so that we can efficiently traverse them later.
if (firstAbsoluteChild == NULL) {
firstAbsoluteChild = child;
}
if (currentAbsoluteChild != NULL) {
currentAbsoluteChild->next_absolute_child = child;
}
currentAbsoluteChild = child;
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis])),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
}
float nextContentDim = 0;
// It only makes sense to consider a child flexible if we have a computed
// dimension for the node->
if (!isUndefined(node->layout.dimensions[dim[mainAxis]]) && isFlex(child)) {
if (isMainDimDefined && isFlex(child)) {
flexibleChildrenCount++;
totalFlexible += getFlex(child);
totalFlexible += child->style.flex;
// Store a private linked list of flexible children so that we can
// efficiently traverse them later.
if (firstFlexChild == NULL) {
firstFlexChild = child;
}
if (currentFlexChild != NULL) {
currentFlexChild->next_flex_child = child;
}
currentFlexChild = child;
// Even if we don't know its exact size yet, we already know the padding,
// border and margin. We'll use this partial information, which represents
@@ -688,14 +741,14 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
} else {
maxWidth = CSS_UNDEFINED;
if (!isRowDirection(mainAxis)) {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
getPaddingAndBorderAxis(node, resolvedRowAxis);
if (!isMainRowDirection) {
if (isDimDefined(node, resolvedRowAxis)) {
maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
getPaddingAndBorderAxis(node, resolvedRowAxis);
paddingAndBorderAxisResolvedRow;
} else {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
paddingAndBorderAxisResolvedRow;
}
}
@@ -706,7 +759,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// Absolute positioned elements do not take part of the layout, so we
// don't use them to compute mainContentDim
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
if (child->style.position_type == CSS_POSITION_RELATIVE) {
nonFlexibleChildrenCount++;
// At this point we know the final size and margin of the element.
nextContentDim = getDimWithMargin(child, mainAxis);
@@ -714,8 +767,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
}
// The element we are about to add would make us go to the next line
if (isFlexWrap(node) &&
!isUndefined(node->layout.dimensions[dim[mainAxis]]) &&
if (isNodeFlexWrap &&
isMainDimDefined &&
mainContentDim + nextContentDim > definedMainDim &&
// If there's only one element, then it's bigger than the content
// and needs its own line
@@ -724,6 +777,44 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
alreadyComputedNextLayout = 1;
break;
}
// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child-> The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(child->style.position_type != CSS_POSITION_RELATIVE || isFlex(child))) {
isSimpleStackMain = false;
firstComplexMain = i;
}
// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child-> The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(child->style.position_type != CSS_POSITION_RELATIVE ||
(alignItem != CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) ||
isUndefined(child->layout.dimensions[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}
if (isSimpleStackMain) {
child->layout.position[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}
mainDim += getDimWithMargin(child, mainAxis);
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}
if (isSimpleStackCross) {
child->layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}
alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
@@ -739,7 +830,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// The remaining available space that needs to be allocated
float remainingMainDim = 0;
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
if (isMainDimDefined) {
remainingMainDim = definedMainDim - mainContentDim;
} else {
remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim;
@@ -752,21 +843,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
float baseMainDim;
float boundMainDim;
// Iterate over every child in the axis. If the flex share of remaining
// space doesn't meet min/max bounds, remove this child from flex
// calculations.
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (isFlex(child)) {
baseMainDim = flexibleMainDim * getFlex(child) +
getPaddingAndBorderAxis(child, mainAxis);
boundMainDim = boundAxis(child, mainAxis, baseMainDim);
// If the flex share of remaining space doesn't meet min/max bounds,
// remove this child from flex calculations.
currentFlexChild = firstFlexChild;
while (currentFlexChild != NULL) {
baseMainDim = flexibleMainDim * currentFlexChild->style.flex +
getPaddingAndBorderAxis(currentFlexChild, mainAxis);
boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim);
if (baseMainDim != boundMainDim) {
remainingMainDim -= boundMainDim;
totalFlexible -= getFlex(child);
}
if (baseMainDim != boundMainDim) {
remainingMainDim -= boundMainDim;
totalFlexible -= currentFlexChild->style.flex;
}
currentFlexChild = currentFlexChild->next_flex_child;
}
flexibleMainDim = remainingMainDim / totalFlexible;
@@ -775,37 +865,37 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
if (flexibleMainDim < 0) {
flexibleMainDim = 0;
}
// We iterate over the full array and only apply the action on flexible
// children. This is faster than actually allocating a new array that
// contains only flexible children.
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (isFlex(child)) {
// At this point we know the final size of the element in the main
// dimension
child->layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis,
flexibleMainDim * getFlex(child) + getPaddingAndBorderAxis(child, mainAxis)
);
maxWidth = CSS_UNDEFINED;
if (isDimDefined(node, resolvedRowAxis)) {
maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
getPaddingAndBorderAxis(node, resolvedRowAxis);
} else if (!isRowDirection(mainAxis)) {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
getPaddingAndBorderAxis(node, resolvedRowAxis);
}
currentFlexChild = firstFlexChild;
while (currentFlexChild != NULL) {
// At this point we know the final size of the element in the main
// dimension
currentFlexChild->layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis,
flexibleMainDim * currentFlexChild->style.flex +
getPaddingAndBorderAxis(currentFlexChild, mainAxis)
);
// And we recursively call the layout algorithm for this child
layoutNode(child, maxWidth, direction);
maxWidth = CSS_UNDEFINED;
if (isDimDefined(node, resolvedRowAxis)) {
maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else if (!isMainRowDirection) {
maxWidth = parentMaxWidth -
getMarginAxis(node, resolvedRowAxis) -
paddingAndBorderAxisResolvedRow;
}
// And we recursively call the layout algorithm for this child
layoutNode(currentFlexChild, maxWidth, direction);
child = currentFlexChild;
currentFlexChild = currentFlexChild->next_flex_child;
child->next_flex_child = NULL;
}
// We use justifyContent to figure out how to allocate the remaining
// space available
} else {
css_justify_t justifyContent = getJustifyContent(node);
} else if (justifyContent != CSS_JUSTIFY_FLEX_START) {
if (justifyContent == CSS_JUSTIFY_CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSS_JUSTIFY_FLEX_END) {
@@ -832,15 +922,12 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
float crossDim = 0;
float mainDim = leadingMainDim +
getLeadingPaddingAndBorder(node, mainAxis);
mainDim += leadingMainDim;
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexMain; i < endLine; ++i) {
child = node->get_child(node->context, i);
child->line_index = linesCount;
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
if (child->style.position_type == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
// In case the child is position absolute and has left/top being
// defined, we override the position to whatever the user said
@@ -854,40 +941,40 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
child->layout.position[pos[mainAxis]] += mainDim;
// Define the trailing position accordingly.
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
if (isMainDimDefined) {
setTrailingPosition(node, child, mainAxis);
}
}
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (child->style.position_type == CSS_POSITION_RELATIVE) {
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}
}
}
float containerCrossAxis = node->layout.dimensions[dim[crossAxis]];
if (isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (!isCrossDimDefined) {
containerCrossAxis = fmaxf(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, crossDim + getPaddingAndBorderAxis(node, crossAxis)),
getPaddingAndBorderAxis(node, crossAxis)
boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
}
// <Loop D> Position elements in the cross axis
for (i = startLine; i < endLine; ++i) {
for (i = firstComplexCross; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
if (child->style.position_type == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[crossAxis])) {
// In case the child is absolutely positionned and has a
// top/left/bottom/right being set, we override all the previously
@@ -897,20 +984,22 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
getLeadingMargin(child, crossAxis);
} else {
float leadingCrossDim = getLeadingPaddingAndBorder(node, crossAxis);
float leadingCrossDim = leadingPaddingAndBorderCross;
// For a relative children, we're either using alignItems (parent) or
// alignSelf (child) in order to determine the position in the cross axis
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
if (child->style.position_type == CSS_POSITION_RELATIVE) {
/*eslint-disable */
// This variable is intentionally re-defined as the code is transpiled to a block scope language
css_align_t alignItem = getAlignItem(node, child);
/*eslint-enable */
if (alignItem == CSS_ALIGN_STRETCH) {
// You can only stretch if the dimension has not already been set
// previously.
if (!isDimDefined(child, crossAxis)) {
if (isUndefined(child->layout.dimensions[dim[crossAxis]])) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
boundAxis(child, crossAxis, containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis)),
paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
@@ -919,8 +1008,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// The remaining space between the parent dimensions+padding and child
// dimensions+margin.
float remainingCrossDim = containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getDimWithMargin(child, crossAxis);
paddingAndBorderAxisCross - getDimWithMargin(child, crossAxis);
if (alignItem == CSS_ALIGN_CENTER) {
leadingCrossDim += remainingCrossDim / 2;
@@ -934,7 +1022,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
child->layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim;
// Define the trailing position accordingly.
if (!isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (isCrossDimDefined) {
setTrailingPosition(node, child, crossAxis);
}
}
@@ -959,16 +1047,15 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm
// section 9.4
//
if (linesCount > 1 &&
!isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (linesCount > 1 && isCrossDimDefined) {
float nodeCrossAxisInnerSize = node->layout.dimensions[dim[crossAxis]] -
getPaddingAndBorderAxis(node, crossAxis);
paddingAndBorderAxisCross;
float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim;
float crossDimLead = 0;
float currentLead = getLeadingPaddingAndBorder(node, crossAxis);
float currentLead = leadingPaddingAndBorderCross;
css_align_t alignContent = getAlignContent(node);
css_align_t alignContent = node->style.align_content;
if (alignContent == CSS_ALIGN_FLEX_END) {
currentLead += remainingAlignContentDim;
} else if (alignContent == CSS_ALIGN_CENTER) {
@@ -985,9 +1072,9 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// compute the line's height and find the endIndex
float lineHeight = 0;
for (ii = startIndex; ii < node->children_count; ++ii) {
for (ii = startIndex; ii < childCount; ++ii) {
child = node->get_child(node->context, ii);
if (getPositionType(child) != CSS_POSITION_RELATIVE) {
if (child->style.position_type != CSS_POSITION_RELATIVE) {
continue;
}
if (child->line_index != i) {
@@ -1005,7 +1092,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
for (ii = startIndex; ii < endIndex; ++ii) {
child = node->get_child(node->context, ii);
if (getPositionType(child) != CSS_POSITION_RELATIVE) {
if (child->style.position_type != CSS_POSITION_RELATIVE) {
continue;
}
@@ -1033,33 +1120,39 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
// If the user didn't specify a width or height, and it has not been set
// by the container, then we set it via the children.
if (isUndefined(node->layout.dimensions[dim[mainAxis]])) {
if (!isMainDimDefined) {
node->layout.dimensions[dim[mainAxis]] = fmaxf(
// We're missing the last padding at this point to get the final
// dimension
boundAxis(node, mainAxis, linesMainDim + getTrailingPaddingAndBorder(node, mainAxis)),
// We can never assign a width smaller than the padding and borders
getPaddingAndBorderAxis(node, mainAxis)
paddingAndBorderAxisMain
);
needsMainTrailingPos = true;
if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsMainTrailingPos = true;
}
}
if (isUndefined(node->layout.dimensions[dim[crossAxis]])) {
if (!isCrossDimDefined) {
node->layout.dimensions[dim[crossAxis]] = fmaxf(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, linesCrossDim + getPaddingAndBorderAxis(node, crossAxis)),
getPaddingAndBorderAxis(node, crossAxis)
boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
needsCrossTrailingPos = true;
if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsCrossTrailingPos = true;
}
}
// <Loop F> Set trailing position if necessary
if (needsMainTrailingPos || needsCrossTrailingPos) {
for (i = 0; i < node->children_count; ++i) {
for (i = 0; i < childCount; ++i) {
child = node->get_child(node->context, i);
if (needsMainTrailingPos) {
@@ -1073,40 +1166,41 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
}
// <Loop G> Calculate dimensions for absolutely positioned elements
for (i = 0; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis])
),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
currentAbsoluteChild = firstAbsoluteChild;
while (currentAbsoluteChild != NULL) {
// Pre-fill dimensions when using absolute position and both offsets for
// the axis are defined (either both left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(currentAbsoluteChild, axis) &&
isPosDefined(currentAbsoluteChild, leading[axis]) &&
isPosDefined(currentAbsoluteChild, trailing[axis])) {
currentAbsoluteChild->layout.dimensions[dim[axis]] = fmaxf(
boundAxis(currentAbsoluteChild, axis, node->layout.dimensions[dim[axis]] -
getBorderAxis(node, axis) -
getMarginAxis(currentAbsoluteChild, axis) -
getPosition(currentAbsoluteChild, leading[axis]) -
getPosition(currentAbsoluteChild, trailing[axis])
),
// You never want to go smaller than padding
getPaddingAndBorderAxis(currentAbsoluteChild, axis)
);
}
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (isPosDefined(child, trailing[axis]) &&
!isPosDefined(child, leading[axis])) {
child->layout.position[leading[axis]] =
node->layout.dimensions[dim[axis]] -
child->layout.dimensions[dim[axis]] -
getPosition(child, trailing[axis]);
}
if (isPosDefined(currentAbsoluteChild, trailing[axis]) &&
!isPosDefined(currentAbsoluteChild, leading[axis])) {
currentAbsoluteChild->layout.position[leading[axis]] =
node->layout.dimensions[dim[axis]] -
currentAbsoluteChild->layout.dimensions[dim[axis]] -
getPosition(currentAbsoluteChild, trailing[axis]);
}
}
child = currentAbsoluteChild;
currentAbsoluteChild = currentAbsoluteChild->next_absolute_child;
child->next_absolute_child = NULL;
}
/** END_GENERATED **/
}

View File

@@ -129,18 +129,22 @@ typedef struct {
float maxDimensions[2];
} css_style_t;
typedef struct css_node {
typedef struct css_node css_node_t;
struct css_node {
css_style_t style;
css_layout_t layout;
int children_count;
int line_index;
css_node_t* next_absolute_child;
css_node_t* next_flex_child;
css_dim_t (*measure)(void *context, float width);
void (*print)(void *context);
struct css_node* (*get_child)(void *context, int i);
bool (*is_dirty)(void *context);
void *context;
} css_node_t;
};
// Lifecycle of nodes and children

File diff suppressed because it is too large Load Diff

View File

@@ -98,13 +98,13 @@ describe('Random layout', function() {
}
function checkRandomLayout(i, node) {
it('should layout randomly #' + i + '.', function(node) {
if (JSON.stringify(computeLayout(node)) !== JSON.stringify(computeDOMLayout(node))) {
node = reduceTest(node);
}
it('should layout randomly #' + i + '.', function(node) {
if (JSON.stringify(computeLayout(node)) !== JSON.stringify(computeDOMLayout(node))) {
node = reduceTest(node);
}
testRandomLayout(node, i);
}.bind(this, node));
testRandomLayout(node, i);
}.bind(this, node));
}
for (var i = 0; i < 100; ++i) {

View File

@@ -7468,6 +7468,40 @@ int main()
test("should layout node with correct start/end border in rtl", root_node, root_layout);
}
{
css_node_t *root_node = new_test_css_node();
{
css_node_t *node_0 = root_node;
node_0->style.dimensions[CSS_WIDTH] = 200;
init_css_node_children(node_0, 1);
{
css_node_t *node_1;
node_1 = node_0->get_child(node_0->context, 0);
node_1->style.dimensions[CSS_WIDTH] = 0;
}
}
css_node_t *root_layout = new_test_css_node();
{
css_node_t *node_0 = root_layout;
node_0->layout.position[CSS_TOP] = 0;
node_0->layout.position[CSS_LEFT] = 0;
node_0->layout.dimensions[CSS_WIDTH] = 200;
node_0->layout.dimensions[CSS_HEIGHT] = 0;
init_css_node_children(node_0, 1);
{
css_node_t *node_1;
node_1 = node_0->get_child(node_0->context, 0);
node_1->layout.position[CSS_TOP] = 0;
node_1->layout.position[CSS_LEFT] = 0;
node_1->layout.dimensions[CSS_WIDTH] = 0;
node_1->layout.dimensions[CSS_HEIGHT] = 0;
}
}
test("should layout node with a 0 width", root_node, root_layout);
}
{
css_node_t *root_node = new_test_css_node();
{

View File

@@ -11,7 +11,6 @@
var testLayout = layoutTestUtils.testLayout;
var testLayoutAgainstDomOnly = layoutTestUtils.testLayoutAgainstDomOnly;
var testFillNodes = layoutTestUtils.testFillNodes;
var testExtractNodes = layoutTestUtils.testExtractNodes;
var text = layoutTestUtils.text;
var texts = layoutTestUtils.texts;
var textSizes = layoutTestUtils.textSizes;
@@ -2298,6 +2297,17 @@ describe('Layout', function() {
);
});
it('should layout node with a 0 width', function() {
testLayout(
{style: {width: 200}, children: [
{style: {width: 0}}
]},
{width: 200, height: 0, top: 0, left: 0, children: [
{width: 0, height: 0, top: 0, left: 0}
]}
);
});
xit('should stretch a nested child', function() {
testLayout(
{children: [

13
src/csharp/.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
root = true
[*]
end_of_line=LF
[*.cs]
indent_style=space
indent_size=4
[*.js]
indent_style=space
indent_size=2

6
src/csharp/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
bin/
obj/
/packages/
/.vs/
*.user
*.nupkg

View File

@@ -0,0 +1,53 @@
/**
* 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.
*/
using System;
using NUnit.Framework;
namespace Facebook.CSSLayout.Tests
{
/**
* Tests for {@link CSSNode}.
*/
public class CSSNodeTest
{
[Test]
public void testAddChildGetParent()
{
CSSNode parent = new CSSNode();
CSSNode child = new CSSNode();
Assert.Null(child.getParent());
Assert.AreEqual(0, parent.getChildCount());
parent.addChildAt(child, 0);
Assert.AreEqual(1, parent.getChildCount());
Assert.AreEqual(child, parent.getChildAt(0));
Assert.AreEqual(parent, child.getParent());
parent.removeChildAt(0);
Assert.Null(child.getParent());
Assert.AreEqual(0, parent.getChildCount());
}
[Test, ExpectedException(typeof(InvalidOperationException))]
public void testCannotAddChildToMultipleParents()
{
CSSNode parent1 = new CSSNode();
CSSNode parent2 = new CSSNode();
CSSNode child = new CSSNode();
parent1.addChildAt(child, 0);
parent2.addChildAt(child, 0);
}
}
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E687C8FD-0A0D-450F-853D-EC301BE1C038}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Facebook.CSSLayout.Tests</RootNamespace>
<AssemblyName>Facebook.CSSLayout.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CSSNodeTest.cs" />
<Compile Include="LayoutCachingTest.cs" />
<Compile Include="LayoutEngineTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestConstants.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Facebook.CSSLayout\Facebook.CSSLayout.csproj">
<Project>{d534fb4b-a7d4-4a29-96d3-f39a91a259bd}</Project>
<Name>Facebook.CSSLayout</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,240 @@
/**
* 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.
*/
using NUnit.Framework;
namespace Facebook.CSSLayout.Tests
{
/**
* Tests for {@link LayoutEngine} and {@link CSSNode} to make sure layouts are only generated when
* needed.
*/
public class LayoutCachingTest
{
private void assertTreeHasNewLayout(bool expectedHasNewLayout, CSSNode root)
{
Assert.AreEqual(expectedHasNewLayout, root.HasNewLayout);
for (int i = 0; i < root.getChildCount(); i++)
{
assertTreeHasNewLayout(expectedHasNewLayout, root.getChildAt(i));
}
}
private void markLayoutAppliedForTree(CSSNode root)
{
root.MarkLayoutSeen();
for (int i = 0; i < root.getChildCount(); i++)
{
markLayoutAppliedForTree(root.getChildAt(i));
}
}
[Test]
public void testCachesFullTree()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
root.calculateLayout();
assertTreeHasNewLayout(true, root);
markLayoutAppliedForTree(root);
root.calculateLayout();
Assert.True(root.HasNewLayout);
assertTreeHasNewLayout(false, c0);
assertTreeHasNewLayout(false, c1);
}
[Test]
public void testInvalidatesCacheWhenChildAdded()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
CSSNode c0c1 = new CSSNode();
CSSNode c1c0 = new CSSNode();
c0c1.Width = 200;
c0c1.Height = 200;
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
c0c0.addChildAt(c1c0, 0);
root.calculateLayout();
markLayoutAppliedForTree(root);
c0.addChildAt(c0c1, 1);
root.calculateLayout();
Assert.True(root.HasNewLayout);
Assert.True(c0.HasNewLayout);
Assert.True(c0c1.HasNewLayout);
Assert.True(c0c0.HasNewLayout);
Assert.True(c1.HasNewLayout);
Assert.False(c1c0.HasNewLayout);
}
[Test]
public void testInvalidatesCacheWhenEnumPropertyChanges()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
root.calculateLayout();
markLayoutAppliedForTree(root);
c1.AlignSelf = CSSAlign.Center;
root.calculateLayout();
Assert.True(root.HasNewLayout);
Assert.True(c1.HasNewLayout);
Assert.True(c0.HasNewLayout);
Assert.False(c0c0.HasNewLayout);
}
[Test]
public void testInvalidatesCacheWhenFloatPropertyChanges()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
root.calculateLayout();
markLayoutAppliedForTree(root);
c1.SetMargin(CSSSpacingType.Left, 10);
root.calculateLayout();
Assert.True(root.HasNewLayout);
Assert.True(c1.HasNewLayout);
Assert.True(c0.HasNewLayout);
Assert.False(c0c0.HasNewLayout);
}
[Test]
public void testInvalidatesFullTreeWhenParentWidthChanges()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
CSSNode c1c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
c1.addChildAt(c1c0, 0);
root.calculateLayout();
markLayoutAppliedForTree(root);
c0.Height = 200;
root.calculateLayout();
Assert.True(root.HasNewLayout);
Assert.True(c0.HasNewLayout);
Assert.True(c0c0.HasNewLayout);
Assert.True(c1.HasNewLayout);
Assert.False(c1c0.HasNewLayout);
}
[Test]
public void testDoesNotInvalidateCacheWhenPropertyIsTheSame()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
root.Width = 200;
root.calculateLayout();
markLayoutAppliedForTree(root);
root.Width = 200;
root.calculateLayout();
Assert.True(root.HasNewLayout);
assertTreeHasNewLayout(false, c0);
assertTreeHasNewLayout(false, c1);
}
[Test]
public void testInvalidateCacheWhenHeightChangesPosition()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c1c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c1.addChildAt(c1c0, 0);
root.calculateLayout();
markLayoutAppliedForTree(root);
c0.Height = 100;
root.calculateLayout();
Assert.True(root.HasNewLayout);
Assert.True(c0.HasNewLayout);
Assert.True(c1.HasNewLayout);
Assert.False(c1c0.HasNewLayout);
}
[Test]
public void testInvalidatesOnNewMeasureFunction()
{
CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode();
CSSNode c0c0 = new CSSNode();
root.addChildAt(c0, 0);
root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0);
root.calculateLayout();
markLayoutAppliedForTree(root);
c1.setMeasureFunction((node, width) => new MeasureOutput(100, 20));
root.calculateLayout();
Assert.True(root.HasNewLayout);
Assert.True(c1.HasNewLayout);
Assert.True(c0.HasNewLayout);
Assert.False(c0c0.HasNewLayout);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
/**
* 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.
*/
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Facebook.CSSLayout.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Facebook.CSSLayout.Tests")]
[assembly: AssemblyCopyright("Copyright © Facebook 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("c186053a-741f-477d-b031-4d343fb20d1d")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,28 @@
/**
* 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.Tests
{
/**
* Generated constants used in {@link LayoutEngineTest}.
*/
public class TestConstants
{
/** START_GENERATED **/
public static readonly float SMALL_WIDTH = 35f;
public static readonly float SMALL_HEIGHT = 18f;
public static readonly float BIG_WIDTH = 172f;
public static readonly float BIG_HEIGHT = 36f;
public static readonly float BIG_MIN_WIDTH = 100f;
public static readonly string SMALL_TEXT = "small";
public static readonly string LONG_TEXT = "loooooooooong with space";
/** END_GENERATED **/
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="2.6.4" targetFramework="net45" />
</packages>

View File

@@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facebook.CSSLayout", "Facebook.CSSLayout\Facebook.CSSLayout.csproj", "{D534FB4B-A7D4-4A29-96D3-F39A91A259BD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facebook.CSSLayout.Tests", "Facebook.CSSLayout.Tests\Facebook.CSSLayout.Tests.csproj", "{E687C8FD-0A0D-450F-853D-EC301BE1C038}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29A6932B-FDDC-4E8A-8895-7FD64CC47B7F}"
ProjectSection(SolutionItems) = preProject
..\CSharpTranspiler.js = ..\CSharpTranspiler.js
..\JavaTranspiler.js = ..\JavaTranspiler.js
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D534FB4B-A7D4-4A29-96D3-F39A91A259BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D534FB4B-A7D4-4A29-96D3-F39A91A259BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D534FB4B-A7D4-4A29-96D3-F39A91A259BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D534FB4B-A7D4-4A29-96D3-F39A91A259BD}.Release|Any CPU.Build.0 = Release|Any CPU
{E687C8FD-0A0D-450F-853D-EC301BE1C038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E687C8FD-0A0D-450F-853D-EC301BE1C038}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E687C8FD-0A0D-450F-853D-EC301BE1C038}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E687C8FD-0A0D-450F-853D-EC301BE1C038}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

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.
*/
using System.Diagnostics;
namespace Facebook.CSSLayout
{
static class Assertions
{
public static T assertNotNull<T>(T v) where T : class
{
Debug.Assert(v != null);
return v;
}
}
}

View File

@@ -0,0 +1,20 @@
/**
* 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 CSSAlign
{
Auto,
FlexStart,
Center,
FlexEnd,
Stretch,
}
}

View File

@@ -0,0 +1,21 @@
/**
* 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 static class CSSConstants
{
public static readonly float Undefined = float.NaN;
public static bool IsUndefined(float value)
{
return float.IsNaN(value);
}
}
}

View File

@@ -0,0 +1,18 @@
/**
* 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 CSSDirection
{
Inherit,
LTR,
RTL
}
}

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.
*/
namespace Facebook.CSSLayout
{
public enum CSSFlexDirection
{
Column,
ColumnReverse,
Row,
RowReverse
}
}

View File

@@ -0,0 +1,20 @@
/**
* 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 CSSJustify
{
FlexStart,
Center,
FlexEnd,
SpaceBetween,
SpaceAround
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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
{
/**
* Where the output of {@link LayoutEngine#layoutNode(CSSNode, float)} will go in the CSSNode.
*/
class CSSLayout
{
public const int POSITION_LEFT = 0;
public const int POSITION_TOP = 1;
public const int POSITION_RIGHT = 2;
public const int POSITION_BOTTOM = 3;
public const int DIMENSION_WIDTH = 0;
public const int DIMENSION_HEIGHT = 1;
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 void resetResult()
{
FillArray(position, 0);
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;
}
public override string ToString()
{
return "layout: {" +
"left: " + position[POSITION_LEFT] + ", " +
"top: " + position[POSITION_TOP] + ", " +
"width: " + dimensions[DIMENSION_WIDTH] + ", " +
"height: " + dimensions[DIMENSION_HEIGHT] + ", " +
"direction: " + direction +
"}";
}
static void FillArray<T>(T[] array, T value)
{
for (var i = 0; i != array.Length; ++i)
array[i] = value;
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* 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
{
/**
* A context for holding values local to a given instance of layout computation.
*
* This is necessary for making layout thread-safe. A separate instance should
* be used when {@link CSSNode#calculateLayout} is called concurrently on
* different node hierarchies.
*/
sealed class CSSLayoutContext
{
/*package*/
public MeasureOutput measureOutput = new MeasureOutput();
}
}

View File

@@ -0,0 +1,542 @@
/**
* 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.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Facebook.CSSLayout
{
/**
* Should measure the given node and put the result in the given MeasureOutput.
*/
public delegate MeasureOutput MeasureFunction(CSSNode node, float width);
/**
* A CSS Node. It has a style object you can manipulate at {@link #style}. After calling
* {@link #calculateLayout()}, {@link #layout} will be filled with the results of the layout.
*/
public class CSSNode
{
const int POSITION_LEFT = CSSLayout.POSITION_LEFT;
const int POSITION_TOP = CSSLayout.POSITION_TOP;
const int POSITION_RIGHT = CSSLayout.POSITION_RIGHT;
const int POSITION_BOTTOM = CSSLayout.POSITION_BOTTOM;
const int DIMENSION_WIDTH = CSSLayout.DIMENSION_WIDTH;
const int DIMENSION_HEIGHT = CSSLayout.DIMENSION_HEIGHT;
enum LayoutState
{
/**
* Some property of this node or its children has changes and the current values in
* {@link #layout} are not valid.
*/
DIRTY,
/**
* This node has a new layout relative to the last time {@link #MarkLayoutSeen()} was called.
*/
HAS_NEW_LAYOUT,
/**
* {@link #layout} is valid for the node's properties and this layout has been marked as
* having been seen.
*/
UP_TO_DATE,
}
internal readonly CSSStyle style = new CSSStyle();
internal readonly CSSLayout layout = new CSSLayout();
internal readonly CachedCSSLayout lastLayout = new CachedCSSLayout();
internal int lineIndex = 0;
internal /*package*/ CSSNode nextAbsoluteChild;
internal /*package*/ CSSNode nextFlexChild;
// 4 is kinda arbitrary, but the default of 10 seems really high for an average View.
readonly List<CSSNode> mChildren = new List<CSSNode>(4);
[Nullable] CSSNode mParent;
[Nullable] MeasureFunction mMeasureFunction = null;
LayoutState mLayoutState = LayoutState.DIRTY;
public int ChildCount
{
get { return mChildren.Count; }
}
public CSSNode this[int i]
{
get { return mChildren[i]; }
}
public IEnumerable<CSSNode> Children
{
get { return mChildren; }
}
public void AddChild(CSSNode child)
{
InsertChild(ChildCount, child);
}
public void InsertChild(int i, CSSNode child)
{
if (child.mParent != null)
{
throw new InvalidOperationException("Child already has a parent, it must be removed first.");
}
mChildren.Insert(i, child);
child.mParent = this;
dirty();
}
public void RemoveChildAt(int i)
{
mChildren[i].mParent = null;
mChildren.RemoveAt(i);
dirty();
}
public CSSNode Parent
{
[return: Nullable]
get
{ return mParent; }
}
/**
* @return the index of the given child, or -1 if the child doesn't exist in this node.
*/
public int IndexOf(CSSNode child)
{
return mChildren.IndexOf(child);
}
public MeasureFunction MeasureFunction
{
get { return mMeasureFunction; }
set
{
if (!valuesEqual(mMeasureFunction, value))
{
mMeasureFunction = value;
dirty();
}
}
}
public bool IsMeasureDefined
{
get { return mMeasureFunction != null; }
}
internal MeasureOutput measure(MeasureOutput measureOutput, float width)
{
if (!IsMeasureDefined)
{
throw new Exception("Measure function isn't defined!");
}
return Assertions.assertNotNull(mMeasureFunction)(this, width);
}
/**
* Performs the actual layout and saves the results in {@link #layout}
*/
public void CalculateLayout()
{
layout.resetResult();
LayoutEngine.layoutNode(DummyLayoutContext, this, CSSConstants.Undefined, null);
}
static readonly CSSLayoutContext DummyLayoutContext = new CSSLayoutContext();
/**
* See {@link LayoutState#DIRTY}.
*/
public bool IsDirty
{
get { return mLayoutState == LayoutState.DIRTY; }
}
/**
* See {@link LayoutState#HAS_NEW_LAYOUT}.
*/
public bool HasNewLayout
{
get { return mLayoutState == LayoutState.HAS_NEW_LAYOUT; }
}
internal protected void dirty()
{
if (mLayoutState == LayoutState.DIRTY)
{
return;
}
else if (mLayoutState == LayoutState.HAS_NEW_LAYOUT)
{
throw new InvalidOperationException("Previous layout was ignored! MarkLayoutSeen() never called");
}
mLayoutState = LayoutState.DIRTY;
if (mParent != null)
{
mParent.dirty();
}
}
internal void markHasNewLayout()
{
mLayoutState = LayoutState.HAS_NEW_LAYOUT;
}
/**
* Tells the node that the current values in {@link #layout} have been seen. Subsequent calls
* to {@link #hasNewLayout()} will return false until this node is laid out with new parameters.
* You must call this each time the layout is generated if the node has a new layout.
*/
public void MarkLayoutSeen()
{
if (!HasNewLayout)
{
throw new InvalidOperationException("Expected node to have a new layout to be seen!");
}
mLayoutState = LayoutState.UP_TO_DATE;
}
void toStringWithIndentation(StringBuilder result, int level)
{
// Spaces and tabs are dropped by IntelliJ logcat integration, so rely on __ instead.
StringBuilder indentation = new StringBuilder();
for (int i = 0; i < level; ++i)
{
indentation.Append("__");
}
result.Append(indentation.ToString());
result.Append(layout.ToString());
if (ChildCount == 0)
{
return;
}
result.Append(", children: [\n");
for (var i = 0; i < ChildCount; i++)
{
this[i].toStringWithIndentation(result, level + 1);
result.Append("\n");
}
result.Append(indentation + "]");
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
this.toStringWithIndentation(sb, 0);
return sb.ToString();
}
protected bool valuesEqual(float f1, float f2)
{
return FloatUtil.floatsEqual(f1, f2);
}
protected bool valuesEqual<T>([Nullable] T o1, [Nullable] T o2)
{
if (o1 == null)
{
return o2 == null;
}
return o1.Equals(o2);
}
public CSSDirection Direction
{
get { return style.direction; }
set { updateDiscreteValue(ref style.direction, value); }
}
public CSSFlexDirection FlexDirection
{
get { return style.flexDirection; }
set { updateDiscreteValue(ref style.flexDirection, value); }
}
public CSSJustify JustifyContent
{
get { return style.justifyContent; }
set { updateDiscreteValue(ref style.justifyContent, value); }
}
public CSSAlign AlignContent
{
get { return style.alignContent; }
set { updateDiscreteValue(ref style.alignContent, value); }
}
public CSSAlign AlignItems
{
get { return style.alignItems; }
set { updateDiscreteValue(ref style.alignItems, value); }
}
public CSSAlign AlignSelf
{
get { return style.alignSelf; }
set { updateDiscreteValue(ref style.alignSelf, value); }
}
public CSSPositionType PositionType
{
get { return style.positionType; }
set { updateDiscreteValue(ref style.positionType, value); }
}
public CSSWrap Wrap
{
get { return style.flexWrap; }
set { updateDiscreteValue(ref style.flexWrap, value); }
}
public float Flex
{
get { return style.flex; }
set { updateFloatValue(ref style.flex, value); }
}
public void SetMargin(CSSSpacingType spacingType, float margin)
{
if (style.margin.set((int)spacingType, margin))
dirty();
}
public float GetMargin(CSSSpacingType spacingType)
{
return style.margin.getRaw((int)spacingType);
}
public void SetPadding(CSSSpacingType spacingType, float padding)
{
if (style.padding.set((int)spacingType, padding))
dirty();
}
public float GetPadding(CSSSpacingType spacingType)
{
return style.padding.getRaw((int)spacingType);
}
public void SetBorder(CSSSpacingType spacingType, float border)
{
if (style.border.set((int)spacingType, border))
dirty();
}
public float GetBorder(CSSSpacingType spacingType)
{
return style.border.getRaw((int)spacingType);
}
public float PositionTop
{
get { return style.position[POSITION_TOP]; }
set { updateFloatValue(ref style.position[POSITION_TOP], value); }
}
public float PositionBottom
{
get { return style.position[POSITION_BOTTOM]; }
set { updateFloatValue(ref style.position[POSITION_BOTTOM], value); }
}
public float PositionLeft
{
get { return style.position[POSITION_LEFT]; }
set { updateFloatValue(ref style.position[POSITION_LEFT], value); }
}
public float PositionRight
{
get { return style.position[POSITION_RIGHT]; }
set { updateFloatValue(ref style.position[POSITION_RIGHT], value); }
}
public float Width
{
get { return style.dimensions[DIMENSION_WIDTH]; }
set { updateFloatValue(ref style.dimensions[DIMENSION_WIDTH], value); }
}
public float Height
{
get { return style.dimensions[DIMENSION_HEIGHT]; }
set { updateFloatValue(ref style.dimensions[DIMENSION_HEIGHT], value); }
}
public float MinWidth
{
get { return style.minWidth; }
set { updateFloatValue(ref style.minWidth, value); }
}
public float MinHeight
{
get { return style.minHeight; }
set { updateFloatValue(ref style.minHeight, value); }
}
public float MaxWidth
{
get { return style.maxWidth; }
set { updateFloatValue(ref style.maxWidth, value); }
}
public float MaxHeight
{
get { return style.maxHeight; }
set { updateFloatValue(ref style.maxHeight, value); }
}
public float LayoutX
{
get { return layout.position[POSITION_LEFT]; }
}
public float LayoutY
{
get { return layout.position[POSITION_TOP]; }
}
public float LayoutWidth
{
get { return layout.dimensions[DIMENSION_WIDTH]; }
}
public float LayoutHeight
{
get { return layout.dimensions[DIMENSION_HEIGHT]; }
}
public CSSDirection LayoutDirection
{
get { return layout.direction; }
}
/**
* Set a default padding (left/top/right/bottom) for this node.
*/
public void SetDefaultPadding(CSSSpacingType spacingType, float padding)
{
if (style.padding.setDefault((int)spacingType, padding))
dirty();
}
void updateDiscreteValue<ValueT>(ref ValueT valueRef, ValueT newValue)
{
if (valuesEqual(valueRef, newValue))
return;
valueRef = newValue;
dirty();
}
void updateFloatValue(ref float valueRef, float newValue)
{
if (valuesEqual(valueRef, newValue))
return;
valueRef = newValue;
dirty();
}
}
public static class CSSNodeExtensions
{
/*
Explicitly mark this node as dirty.
Calling this function is required when the measure function points to the same instance,
but changes its behavior.
For all other property changes, the node is automatically marked dirty.
*/
public static void MarkDirty(this CSSNode node)
{
node.dirty();
}
}
internal static class CSSNodeExtensionsInternal
{
public static CSSNode getParent(this CSSNode node)
{
return node.Parent;
}
public static int getChildCount(this CSSNode node)
{
return node.ChildCount;
}
public static CSSNode getChildAt(this CSSNode node, int i)
{
return node[i];
}
public static void addChildAt(this CSSNode node, CSSNode child, int i)
{
node.InsertChild(i, child);
}
public static void removeChildAt(this CSSNode node, int i)
{
node.RemoveChildAt(i);
}
public static void setMeasureFunction(this CSSNode node, MeasureFunction measureFunction)
{
node.MeasureFunction = measureFunction;
}
public static void calculateLayout(this CSSNode node)
{
node.CalculateLayout();
}
public static bool isDirty(this CSSNode node)
{
return node.IsDirty;
}
public static void setMargin(this CSSNode node, int spacingType, float margin)
{
node.SetMargin((CSSSpacingType)spacingType, margin);
}
public static void setPadding(this CSSNode node, int spacingType, float padding)
{
node.SetPadding((CSSSpacingType)spacingType, padding);
}
public static void setBorder(this CSSNode node, int spacingType, float border)
{
node.SetBorder((CSSSpacingType)spacingType, border);
}
}
}

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 CSSPositionType
{
Relative,
Absolute
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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 CSSSpacingType
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3,
Vertical = 4,
Horizontal = 5,
Start = 6,
End = 7,
All = 8
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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
{
/**
* The CSS style definition for a {@link CSSNode}.
*/
sealed class CSSStyle
{
public CSSDirection direction = CSSDirection.Inherit;
public CSSFlexDirection flexDirection = CSSFlexDirection.Column;
public CSSJustify justifyContent = CSSJustify.FlexStart;
public CSSAlign alignContent = CSSAlign.FlexStart;
public CSSAlign alignItems = CSSAlign.Stretch;
public CSSAlign alignSelf = CSSAlign.Auto;
public CSSPositionType positionType = CSSPositionType.Relative;
public CSSWrap flexWrap = CSSWrap.NoWrap;
public float flex;
public Spacing margin = new Spacing();
public Spacing padding = new Spacing();
public Spacing border = new Spacing();
public float[] position = {
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined
};
public float[] dimensions = {
CSSConstants.Undefined,
CSSConstants.Undefined
};
public float minWidth = CSSConstants.Undefined;
public float minHeight = CSSConstants.Undefined;
public float maxWidth = CSSConstants.Undefined;
public float maxHeight = CSSConstants.Undefined;
}
}

View File

@@ -0,0 +1,16 @@
/**
* 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 CSSWrap
{
NoWrap,
Wrap
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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
{
/**
* CSSLayout with additional information about the conditions under which it was generated.
* {@link #RequestedWidth} and {@link #RequestedHeight} are the width and height the parent set on
* this node before calling layout visited us.
*/
class CachedCSSLayout : CSSLayout
{
public float requestedWidth = CSSConstants.Undefined;
public float requestedHeight = CSSConstants.Undefined;
public float parentMaxWidth = CSSConstants.Undefined;
}
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D534FB4B-A7D4-4A29-96D3-F39A91A259BD}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Facebook.CSSLayout</RootNamespace>
<AssemblyName>Facebook.CSSLayout</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile259</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>5</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="Assertions.cs" />
<Compile Include="CachedCSSLayout.cs" />
<Compile Include="CSSAlign.cs" />
<Compile Include="CSSConstants.cs" />
<Compile Include="CSSDirection.cs" />
<Compile Include="CSSFlexDirection.cs" />
<Compile Include="CSSJustify.cs" />
<Compile Include="CSSLayout.cs" />
<Compile Include="CSSLayoutContext.cs" />
<Compile Include="CSSNode.cs" />
<Compile Include="CSSPositionType.cs" />
<Compile Include="CSSStyle.cs" />
<Compile Include="CSSWrap.cs" />
<Compile Include="FloatUtil.cs" />
<Compile Include="LayoutEngine.cs" />
<Compile Include="MeasureOutput.cs" />
<Compile Include="NullableAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Spacing.cs" />
<Compile Include="CSSSpacingType.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>$author$</authors>
<owners>$author$</owners>
<licenseUrl>https://github.com/facebook/css-layout/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/facebook/css-layout</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<releaseNotes></releaseNotes>
<copyright>Copyright 2015 Facebook</copyright>
<tags>flexbox flex-box css layout css-layout facebook</tags>
</metadata>
</package>

View File

@@ -0,0 +1,27 @@
/**
* 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.
*/
using System;
namespace Facebook.CSSLayout
{
static class FloatUtil
{
const float Epsilon = .00001f;
public static bool floatsEqual(float f1, float f2)
{
if (float.IsNaN(f1) || float.IsNaN(f2))
{
return float.IsNaN(f1) && float.IsNaN(f2);
}
return Math.Abs(f2 - f1) < Epsilon;
}
}
}

View File

@@ -0,0 +1,937 @@
/**
* 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.
*/
using System;
using boolean = System.Boolean;
namespace Facebook.CSSLayout
{
/**
* Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float)}.
*/
static class LayoutEngine
{
const int POSITION_LEFT = CSSLayout.POSITION_LEFT;
const int POSITION_TOP = CSSLayout.POSITION_TOP;
const int POSITION_RIGHT = CSSLayout.POSITION_RIGHT;
const int POSITION_BOTTOM = CSSLayout.POSITION_BOTTOM;
const int DIMENSION_WIDTH = CSSLayout.DIMENSION_WIDTH;
const int DIMENSION_HEIGHT = CSSLayout.DIMENSION_HEIGHT;
const int CSS_FLEX_DIRECTION_COLUMN =
(int)CSSFlexDirection.Column;
const int CSS_FLEX_DIRECTION_COLUMN_REVERSE =
(int)CSSFlexDirection.ColumnReverse;
const int CSS_FLEX_DIRECTION_ROW =
(int)CSSFlexDirection.Row;
const int CSS_FLEX_DIRECTION_ROW_REVERSE =
(int)CSSFlexDirection.RowReverse;
const int CSS_POSITION_RELATIVE = (int)CSSPositionType.Relative;
const int CSS_POSITION_ABSOLUTE = (int)CSSPositionType.Absolute;
private static readonly int[] leading = {
POSITION_TOP,
POSITION_BOTTOM,
POSITION_LEFT,
POSITION_RIGHT,
};
private static readonly int[] trailing = {
POSITION_BOTTOM,
POSITION_TOP,
POSITION_RIGHT,
POSITION_LEFT,
};
private static readonly int[] pos = {
POSITION_TOP,
POSITION_BOTTOM,
POSITION_LEFT,
POSITION_RIGHT,
};
private static readonly int[] dim = {
DIMENSION_HEIGHT,
DIMENSION_HEIGHT,
DIMENSION_WIDTH,
DIMENSION_WIDTH,
};
private static readonly int[] leadingSpacing = {
Spacing.TOP,
Spacing.BOTTOM,
Spacing.START,
Spacing.START
};
private static readonly int[] trailingSpacing = {
Spacing.BOTTOM,
Spacing.TOP,
Spacing.END,
Spacing.END
};
private static float boundAxis(CSSNode node, int axis, float value)
{
float min = CSSConstants.Undefined;
float max = CSSConstants.Undefined;
if (axis == CSS_FLEX_DIRECTION_COLUMN || axis == CSS_FLEX_DIRECTION_COLUMN_REVERSE)
{
min = node.style.minHeight;
max = node.style.maxHeight;
}
else if (axis == CSS_FLEX_DIRECTION_ROW || axis == CSS_FLEX_DIRECTION_ROW_REVERSE)
{
min = node.style.minWidth;
max = node.style.maxWidth;
}
float boundValue = value;
if (!float.IsNaN(max) && max >= 0.0 && boundValue > max)
{
boundValue = max;
}
if (!float.IsNaN(min) && min >= 0.0 && boundValue < min)
{
boundValue = min;
}
return boundValue;
}
private static void setDimensionFromStyle(CSSNode node, int axis)
{
// The parent already computed us a width or height. We just skip it
if (!float.IsNaN(node.layout.dimensions[dim[axis]]))
{
return;
}
// We only run if there's a width or height defined
if (float.IsNaN(node.style.dimensions[dim[axis]]) ||
node.style.dimensions[dim[axis]] <= 0.0)
{
return;
}
// The dimensions can never be smaller than the padding and border
float maxLayoutDimension = Math.Max(
boundAxis(node, axis, node.style.dimensions[dim[axis]]),
node.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) +
node.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) +
node.style.border.getWithFallback(leadingSpacing[axis], leading[axis]) +
node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis]));
node.layout.dimensions[dim[axis]] = maxLayoutDimension;
}
private static float getRelativePosition(CSSNode node, int axis)
{
float lead = node.style.position[leading[axis]];
if (!float.IsNaN(lead))
{
return lead;
}
float trailingPos = node.style.position[trailing[axis]];
return float.IsNaN(trailingPos) ? 0 : -trailingPos;
}
static int resolveAxis(int axis, CSSDirection direction)
{
if (direction == CSSDirection.RTL)
{
if (axis == CSS_FLEX_DIRECTION_ROW)
{
return CSS_FLEX_DIRECTION_ROW_REVERSE;
}
else if (axis == CSS_FLEX_DIRECTION_ROW_REVERSE)
{
return CSS_FLEX_DIRECTION_ROW;
}
}
return axis;
}
static CSSDirection resolveDirection(CSSNode node, CSSDirection? parentDirection)
{
CSSDirection direction = node.style.direction;
if (direction == CSSDirection.Inherit)
{
direction = (parentDirection == null ? CSSDirection.LTR : parentDirection.Value);
}
return direction;
}
static int getFlexDirection(CSSNode node)
{
return (int)node.style.flexDirection;
}
private static int getCrossFlexDirection(int axis, CSSDirection direction)
{
if (axis == CSS_FLEX_DIRECTION_COLUMN || axis == CSS_FLEX_DIRECTION_COLUMN_REVERSE)
{
return resolveAxis(CSS_FLEX_DIRECTION_ROW, direction);
}
else
{
return CSS_FLEX_DIRECTION_COLUMN;
}
}
static CSSAlign getAlignItem(CSSNode node, CSSNode child)
{
if (child.style.alignSelf != CSSAlign.Auto)
{
return child.style.alignSelf;
}
return node.style.alignItems;
}
static boolean isMeasureDefined(CSSNode node)
{
return node.IsMeasureDefined;
}
static boolean needsRelayout(CSSNode node, float parentMaxWidth)
{
return node.isDirty() ||
!FloatUtil.floatsEqual(
node.lastLayout.requestedHeight,
node.layout.dimensions[DIMENSION_HEIGHT]) ||
!FloatUtil.floatsEqual(
node.lastLayout.requestedWidth,
node.layout.dimensions[DIMENSION_WIDTH]) ||
!FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth);
}
internal static void layoutNode(CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, CSSDirection? parentDirection)
{
if (needsRelayout(node, parentMaxWidth))
{
node.lastLayout.requestedWidth = node.layout.dimensions[DIMENSION_WIDTH];
node.lastLayout.requestedHeight = node.layout.dimensions[DIMENSION_HEIGHT];
node.lastLayout.parentMaxWidth = parentMaxWidth;
layoutNodeImpl(layoutContext, node, parentMaxWidth, parentDirection);
node.lastLayout.copy(node.layout);
}
else
{
node.layout.copy(node.lastLayout);
}
node.markHasNewLayout();
}
static void layoutNodeImpl(CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, CSSDirection? parentDirection)
{
var childCount_ = node.getChildCount();
for (int i_ = 0; i_ < childCount_; i_++)
{
node.getChildAt(i_).layout.resetResult();
}
/** START_GENERATED **/
CSSDirection direction = resolveDirection(node, parentDirection);
int mainAxis = resolveAxis(getFlexDirection(node), direction);
int crossAxis = getCrossFlexDirection(mainAxis, direction);
int resolvedRowAxis = resolveAxis(CSS_FLEX_DIRECTION_ROW, direction);
// Handle width and height style attributes
setDimensionFromStyle(node, mainAxis);
setDimensionFromStyle(node, crossAxis);
// Set the resolved resolution in the node's layout
node.layout.direction = direction;
// The position is set by the parent, but we need to complete it with a
// delta composed of the margin and left/top/right/bottom
node.layout.position[leading[mainAxis]] += node.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) +
getRelativePosition(node, mainAxis);
node.layout.position[trailing[mainAxis]] += node.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) +
getRelativePosition(node, mainAxis);
node.layout.position[leading[crossAxis]] += node.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) +
getRelativePosition(node, crossAxis);
node.layout.position[trailing[crossAxis]] += node.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) +
getRelativePosition(node, crossAxis);
// Inline immutable values from the target node to avoid excessive method
// invocations during the layout calculation.
int childCount = node.getChildCount();
float paddingAndBorderAxisResolvedRow = ((node.style.padding.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.border.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis])) + (node.style.padding.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]) + node.style.border.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])));
if (isMeasureDefined(node)) {
boolean isResolvedRowDimDefined = !float.IsNaN(node.layout.dimensions[dim[resolvedRowAxis]]);
float width = CSSConstants.Undefined;
if ((!float.IsNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
width = node.style.dimensions[DIMENSION_WIDTH];
} else if (isResolvedRowDimDefined) {
width = node.layout.dimensions[dim[resolvedRowAxis]];
} else {
width = parentMaxWidth -
(node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]));
}
width -= paddingAndBorderAxisResolvedRow;
// We only need to give a dimension for the text if we haven't got any
// for it computed yet. It can either be from the style attribute or because
// the element is flexible.
boolean isRowUndefined = !(!float.IsNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0) && !isResolvedRowDimDefined;
boolean isColumnUndefined = !(!float.IsNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0) &&
float.IsNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]);
// Let's not measure the text if we already know both dimensions
if (isRowUndefined || isColumnUndefined) {
MeasureOutput measureDim = node.measure(
layoutContext.measureOutput,
width
);
if (isRowUndefined) {
node.layout.dimensions[DIMENSION_WIDTH] = measureDim.width +
paddingAndBorderAxisResolvedRow;
}
if (isColumnUndefined) {
node.layout.dimensions[DIMENSION_HEIGHT] = measureDim.height +
((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])));
}
}
if (childCount == 0) {
return;
}
}
boolean isNodeFlexWrap = (node.style.flexWrap == CSSWrap.Wrap);
CSSJustify justifyContent = node.style.justifyContent;
float leadingPaddingAndBorderMain = (node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]));
float leadingPaddingAndBorderCross = (node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]));
float paddingAndBorderAxisMain = ((node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])));
float paddingAndBorderAxisCross = ((node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])));
boolean isMainDimDefined = !float.IsNaN(node.layout.dimensions[dim[mainAxis]]);
boolean isCrossDimDefined = !float.IsNaN(node.layout.dimensions[dim[crossAxis]]);
boolean isMainRowDirection = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE);
int i;
int ii;
CSSNode child;
int axis;
CSSNode firstAbsoluteChild = null;
CSSNode currentAbsoluteChild = null;
float definedMainDim = CSSConstants.Undefined;
if (isMainDimDefined) {
definedMainDim = node.layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain;
}
// We want to execute the next two loops one per line with flex-wrap
int startLine = 0;
int endLine = 0;
// int nextOffset = 0;
int alreadyComputedNextLayout = 0;
// We aggregate the total dimensions of the container in those two variables
float linesCrossDim = 0;
float linesMainDim = 0;
int linesCount = 0;
while (endLine < childCount) {
// <Loop A> Layout non flexible children and count children by type
// mainContentDim is accumulation of the dimensions and margin of all the
// non flexible children. This will be used in order to either set the
// dimensions of the node if none already exist, or to compute the
// remaining space left for the flexible children.
float mainContentDim = 0;
// There are three kind of children, non flexible, flexible and absolute.
// We need to know how many there are in order to distribute the space.
int flexibleChildrenCount = 0;
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;
// Use the line loop to position children in the main axis for as long
// as they are using a simple stacking behaviour. Children that are
// immediately stacked in the initial loop will not be touched again
// in <Loop C>.
boolean isSimpleStackMain =
(isMainDimDefined && justifyContent == CSSJustify.FlexStart) ||
(!isMainDimDefined && justifyContent != CSSJustify.Center);
int firstComplexMain = (isSimpleStackMain ? childCount : startLine);
// Use the initial line loop to position children in the cross axis for
// as long as they are relatively positioned with alignment STRETCH or
// FLEX_START. Children that are immediately stacked in the initial loop
// will not be touched again in <Loop D>.
boolean isSimpleStackCross = true;
int firstComplexCross = childCount;
CSSNode firstFlexChild = null;
CSSNode currentFlexChild = null;
float mainDim = leadingPaddingAndBorderMain;
float crossDim = 0;
float maxWidth;
for (i = startLine; i < childCount; ++i) {
child = node.getChildAt(i);
child.lineIndex = linesCount;
child.nextAbsoluteChild = null;
child.nextFlexChild = null;
CSSAlign alignItem = getAlignItem(node, child);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (alignItem == CSSAlign.Stretch &&
child.style.positionType == CSSPositionType.Relative &&
isCrossDimDefined &&
!(!float.IsNaN(child.style.dimensions[dim[crossAxis]]) && child.style.dimensions[dim[crossAxis]] >= 0.0)) {
child.layout.dimensions[dim[crossAxis]] = Math.Max(
boundAxis(child, crossAxis, node.layout.dimensions[dim[crossAxis]] -
paddingAndBorderAxisCross - (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))),
// You never want to go smaller than padding
((child.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))
);
} else if (child.style.positionType == CSSPositionType.Absolute) {
// Store a private linked list of absolutely positioned children
// so that we can efficiently traverse them later.
if (firstAbsoluteChild == null) {
firstAbsoluteChild = child;
}
if (currentAbsoluteChild != null) {
currentAbsoluteChild.nextAbsoluteChild = child;
}
currentAbsoluteChild = child;
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!float.IsNaN(node.layout.dimensions[dim[axis]]) &&
!(!float.IsNaN(child.style.dimensions[dim[axis]]) && child.style.dimensions[dim[axis]] >= 0.0) &&
!float.IsNaN(child.style.position[leading[axis]]) &&
!float.IsNaN(child.style.position[trailing[axis]])) {
child.layout.dimensions[dim[axis]] = Math.Max(
boundAxis(child, axis, node.layout.dimensions[dim[axis]] -
((node.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + node.style.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (node.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis]))) -
(child.style.margin.getWithFallback(leadingSpacing[axis], leading[axis]) + child.style.margin.getWithFallback(trailingSpacing[axis], trailing[axis])) -
(float.IsNaN(child.style.position[leading[axis]]) ? 0 : child.style.position[leading[axis]]) -
(float.IsNaN(child.style.position[trailing[axis]]) ? 0 : child.style.position[trailing[axis]])),
// You never want to go smaller than padding
((child.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + child.style.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (child.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + child.style.border.getWithFallback(trailingSpacing[axis], trailing[axis])))
);
}
}
}
float nextContentDim = 0;
// It only makes sense to consider a child flexible if we have a computed
// dimension for the node.
if (isMainDimDefined && (child.style.positionType == CSSPositionType.Relative && child.style.flex > 0)) {
flexibleChildrenCount++;
totalFlexible += child.style.flex;
// Store a private linked list of flexible children so that we can
// efficiently traverse them later.
if (firstFlexChild == null) {
firstFlexChild = child;
}
if (currentFlexChild != null) {
currentFlexChild.nextFlexChild = child;
}
currentFlexChild = child;
// Even if we don't know its exact size yet, we already know the padding,
// border and margin. We'll use this partial information, which represents
// the smallest possible size for the child, to compute the remaining
// available space.
nextContentDim = ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))) +
(child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
} else {
maxWidth = CSSConstants.Undefined;
if (!isMainRowDirection) {
if ((!float.IsNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else {
maxWidth = parentMaxWidth -
(node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) -
paddingAndBorderAxisResolvedRow;
}
}
// This is the main recursive call. We layout non flexible children.
if (alreadyComputedNextLayout == 0) {
layoutNode(layoutContext, child, maxWidth, direction);
}
// Absolute positioned elements do not take part of the layout, so we
// don't use them to compute mainContentDim
if (child.style.positionType == CSSPositionType.Relative) {
nonFlexibleChildrenCount++;
// At this point we know the final size and margin of the element.
nextContentDim = (child.layout.dimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
}
}
// The element we are about to add would make us go to the next line
if (isNodeFlexWrap &&
isMainDimDefined &&
mainContentDim + nextContentDim > definedMainDim &&
// If there's only one element, then it's bigger than the content
// and needs its own line
i != startLine) {
nonFlexibleChildrenCount--;
alreadyComputedNextLayout = 1;
break;
}
// Disable simple stacking in the main axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop C>.
if (isSimpleStackMain &&
(child.style.positionType != CSSPositionType.Relative || (child.style.positionType == CSSPositionType.Relative && child.style.flex > 0))) {
isSimpleStackMain = false;
firstComplexMain = i;
}
// Disable simple stacking in the cross axis for the current line as
// we found a non-trivial child. The remaining children will be laid out
// in <Loop D>.
if (isSimpleStackCross &&
(child.style.positionType != CSSPositionType.Relative ||
(alignItem != CSSAlign.Stretch && alignItem != CSSAlign.FlexStart) ||
float.IsNaN(child.layout.dimensions[dim[crossAxis]]))) {
isSimpleStackCross = false;
firstComplexCross = i;
}
if (isSimpleStackMain) {
child.layout.position[pos[mainAxis]] += mainDim;
if (isMainDimDefined) {
child.layout.position[trailing[mainAxis]] = node.layout.dimensions[dim[mainAxis]] - child.layout.dimensions[dim[mainAxis]] - child.layout.position[pos[mainAxis]];
}
mainDim += (child.layout.dimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
crossDim = Math.Max(crossDim, boundAxis(child, crossAxis, (child.layout.dimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))));
}
if (isSimpleStackCross) {
child.layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;
if (isCrossDimDefined) {
child.layout.position[trailing[crossAxis]] = node.layout.dimensions[dim[crossAxis]] - child.layout.dimensions[dim[crossAxis]] - child.layout.position[pos[crossAxis]];
}
}
alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
}
// <Loop B> Layout flexible children and allocate empty space
// In order to position the elements in the main axis, we have two
// controls. The space between the beginning and the first element
// and the space between each two elements.
float leadingMainDim = 0;
float betweenMainDim = 0;
// The remaining available space that needs to be allocated
float remainingMainDim = 0;
if (isMainDimDefined) {
remainingMainDim = definedMainDim - mainContentDim;
} else {
remainingMainDim = Math.Max(mainContentDim, 0) - mainContentDim;
}
// If there are flexible children in the mix, they are going to fill the
// remaining space
if (flexibleChildrenCount != 0) {
float flexibleMainDim = remainingMainDim / totalFlexible;
float baseMainDim;
float boundMainDim;
// If the flex share of remaining space doesn't meet min/max bounds,
// remove this child from flex calculations.
currentFlexChild = firstFlexChild;
while (currentFlexChild != null) {
baseMainDim = flexibleMainDim * currentFlexChild.style.flex +
((currentFlexChild.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + currentFlexChild.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (currentFlexChild.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + currentFlexChild.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])));
boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim);
if (baseMainDim != boundMainDim) {
remainingMainDim -= boundMainDim;
totalFlexible -= currentFlexChild.style.flex;
}
currentFlexChild = currentFlexChild.nextFlexChild;
}
flexibleMainDim = remainingMainDim / totalFlexible;
// The non flexible children can overflow the container, in this case
// we should just assume that there is no space available.
if (flexibleMainDim < 0) {
flexibleMainDim = 0;
}
currentFlexChild = firstFlexChild;
while (currentFlexChild != null) {
// At this point we know the final size of the element in the main
// dimension
currentFlexChild.layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis,
flexibleMainDim * currentFlexChild.style.flex +
((currentFlexChild.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + currentFlexChild.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (currentFlexChild.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + currentFlexChild.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])))
);
maxWidth = CSSConstants.Undefined;
if ((!float.IsNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else if (!isMainRowDirection) {
maxWidth = parentMaxWidth -
(node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) -
paddingAndBorderAxisResolvedRow;
}
// And we recursively call the layout algorithm for this child
layoutNode(layoutContext, currentFlexChild, maxWidth, direction);
child = currentFlexChild;
currentFlexChild = currentFlexChild.nextFlexChild;
child.nextFlexChild = null;
}
// We use justifyContent to figure out how to allocate the remaining
// space available
} else if (justifyContent != CSSJustify.FlexStart) {
if (justifyContent == CSSJustify.Center) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSSJustify.FlexEnd) {
leadingMainDim = remainingMainDim;
} else if (justifyContent == CSSJustify.SpaceBetween) {
remainingMainDim = Math.Max(remainingMainDim, 0);
if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) {
betweenMainDim = remainingMainDim /
(flexibleChildrenCount + nonFlexibleChildrenCount - 1);
} else {
betweenMainDim = 0;
}
} else if (justifyContent == CSSJustify.SpaceAround) {
// Space on the edges is half of the space between elements
betweenMainDim = remainingMainDim /
(flexibleChildrenCount + nonFlexibleChildrenCount);
leadingMainDim = betweenMainDim / 2;
}
}
// <Loop C> Position elements in the main axis and compute dimensions
// At this point, all the children have their dimensions set. We need to
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
mainDim += leadingMainDim;
for (i = firstComplexMain; i < endLine; ++i) {
child = node.getChildAt(i);
if (child.style.positionType == CSSPositionType.Absolute &&
!float.IsNaN(child.style.position[leading[mainAxis]])) {
// In case the child is position absolute and has left/top being
// defined, we override the position to whatever the user said
// (and margin/border).
child.layout.position[pos[mainAxis]] = (float.IsNaN(child.style.position[leading[mainAxis]]) ? 0 : child.style.position[leading[mainAxis]]) +
node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) +
child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]);
} else {
// If the child is position absolute (without top/left) or relative,
// we put it at the current accumulated offset.
child.layout.position[pos[mainAxis]] += mainDim;
// Define the trailing position accordingly.
if (isMainDimDefined) {
child.layout.position[trailing[mainAxis]] = node.layout.dimensions[dim[mainAxis]] - child.layout.dimensions[dim[mainAxis]] - child.layout.position[pos[mainAxis]];
}
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (child.style.positionType == CSSPositionType.Relative) {
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + (child.layout.dimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]));
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = Math.Max(crossDim, boundAxis(child, crossAxis, (child.layout.dimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))));
}
}
}
float containerCrossAxis = node.layout.dimensions[dim[crossAxis]];
if (!isCrossDimDefined) {
containerCrossAxis = Math.Max(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
}
// <Loop D> Position elements in the cross axis
for (i = firstComplexCross; i < endLine; ++i) {
child = node.getChildAt(i);
if (child.style.positionType == CSSPositionType.Absolute &&
!float.IsNaN(child.style.position[leading[crossAxis]])) {
// In case the child is absolutely positionned and has a
// top/left/bottom/right being set, we override all the previously
// computed positions to set it correctly.
child.layout.position[pos[crossAxis]] = (float.IsNaN(child.style.position[leading[crossAxis]]) ? 0 : child.style.position[leading[crossAxis]]) +
node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) +
child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]);
} else {
float leadingCrossDim = leadingPaddingAndBorderCross;
// For a relative children, we're either using alignItems (parent) or
// alignSelf (child) in order to determine the position in the cross axis
if (child.style.positionType == CSSPositionType.Relative) {
/*eslint-disable */
// This variable is intentionally re-defined as the code is transpiled to a block scope language
CSSAlign alignItem = getAlignItem(node, child);
/*eslint-enable */
if (alignItem == CSSAlign.Stretch) {
// You can only stretch if the dimension has not already been set
// previously.
if (float.IsNaN(child.layout.dimensions[dim[crossAxis]])) {
child.layout.dimensions[dim[crossAxis]] = Math.Max(
boundAxis(child, crossAxis, containerCrossAxis -
paddingAndBorderAxisCross - (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))),
// You never want to go smaller than padding
((child.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))
);
}
} else if (alignItem != CSSAlign.FlexStart) {
// The remaining space between the parent dimensions+padding and child
// dimensions+margin.
float remainingCrossDim = containerCrossAxis -
paddingAndBorderAxisCross - (child.layout.dimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]));
if (alignItem == CSSAlign.Center) {
leadingCrossDim += remainingCrossDim / 2;
} else { // CSSAlign.FlexEnd
leadingCrossDim += remainingCrossDim;
}
}
}
// And we apply the position
child.layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim;
// Define the trailing position accordingly.
if (isCrossDimDefined) {
child.layout.position[trailing[crossAxis]] = node.layout.dimensions[dim[crossAxis]] - child.layout.dimensions[dim[crossAxis]] - child.layout.position[pos[crossAxis]];
}
}
}
linesCrossDim += crossDim;
linesMainDim = Math.Max(linesMainDim, mainDim);
linesCount += 1;
startLine = endLine;
}
// <Loop E>
//
// Note(prenaux): More than one line, we need to layout the crossAxis
// according to alignContent.
//
// Note that we could probably remove <Loop D> and handle the one line case
// here too, but for the moment this is safer since it won't interfere with
// previously working code.
//
// See specs:
// http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm
// section 9.4
//
if (linesCount > 1 && isCrossDimDefined) {
float nodeCrossAxisInnerSize = node.layout.dimensions[dim[crossAxis]] -
paddingAndBorderAxisCross;
float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim;
float crossDimLead = 0;
float currentLead = leadingPaddingAndBorderCross;
CSSAlign alignContent = node.style.alignContent;
if (alignContent == CSSAlign.FlexEnd) {
currentLead += remainingAlignContentDim;
} else if (alignContent == CSSAlign.Center) {
currentLead += remainingAlignContentDim / 2;
} else if (alignContent == CSSAlign.Stretch) {
if (nodeCrossAxisInnerSize > linesCrossDim) {
crossDimLead = (remainingAlignContentDim / linesCount);
}
}
int endIndex = 0;
for (i = 0; i < linesCount; ++i) {
int startIndex = endIndex;
// compute the line's height and find the endIndex
float lineHeight = 0;
for (ii = startIndex; ii < childCount; ++ii) {
child = node.getChildAt(ii);
if (child.style.positionType != CSSPositionType.Relative) {
continue;
}
if (child.lineIndex != i) {
break;
}
if (!float.IsNaN(child.layout.dimensions[dim[crossAxis]])) {
lineHeight = Math.Max(
lineHeight,
child.layout.dimensions[dim[crossAxis]] + (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))
);
}
}
endIndex = ii;
lineHeight += crossDimLead;
for (ii = startIndex; ii < endIndex; ++ii) {
child = node.getChildAt(ii);
if (child.style.positionType != CSSPositionType.Relative) {
continue;
}
CSSAlign alignContentAlignItem = getAlignItem(node, child);
if (alignContentAlignItem == CSSAlign.FlexStart) {
child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]);
} else if (alignContentAlignItem == CSSAlign.FlexEnd) {
child.layout.position[pos[crossAxis]] = currentLead + lineHeight - child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) - child.layout.dimensions[dim[crossAxis]];
} else if (alignContentAlignItem == CSSAlign.Center) {
float childHeight = child.layout.dimensions[dim[crossAxis]];
child.layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2;
} else if (alignContentAlignItem == CSSAlign.Stretch) {
child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]);
// TODO(prenaux): Correctly set the height of items with undefined
// (auto) crossAxis dimension.
}
}
currentLead += lineHeight;
}
}
boolean needsMainTrailingPos = false;
boolean needsCrossTrailingPos = false;
// If the user didn't specify a width or height, and it has not been set
// by the container, then we set it via the children.
if (!isMainDimDefined) {
node.layout.dimensions[dim[mainAxis]] = Math.Max(
// We're missing the last padding at this point to get the final
// dimension
boundAxis(node, mainAxis, linesMainDim + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))),
// We can never assign a width smaller than the padding and borders
paddingAndBorderAxisMain
);
if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsMainTrailingPos = true;
}
}
if (!isCrossDimDefined) {
node.layout.dimensions[dim[crossAxis]] = Math.Max(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross),
paddingAndBorderAxisCross
);
if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE ||
crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
needsCrossTrailingPos = true;
}
}
// <Loop F> Set trailing position if necessary
if (needsMainTrailingPos || needsCrossTrailingPos) {
for (i = 0; i < childCount; ++i) {
child = node.getChildAt(i);
if (needsMainTrailingPos) {
child.layout.position[trailing[mainAxis]] = node.layout.dimensions[dim[mainAxis]] - child.layout.dimensions[dim[mainAxis]] - child.layout.position[pos[mainAxis]];
}
if (needsCrossTrailingPos) {
child.layout.position[trailing[crossAxis]] = node.layout.dimensions[dim[crossAxis]] - child.layout.dimensions[dim[crossAxis]] - child.layout.position[pos[crossAxis]];
}
}
}
// <Loop G> Calculate dimensions for absolutely positioned elements
currentAbsoluteChild = firstAbsoluteChild;
while (currentAbsoluteChild != null) {
// Pre-fill dimensions when using absolute position and both offsets for
// the axis are defined (either both left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!float.IsNaN(node.layout.dimensions[dim[axis]]) &&
!(!float.IsNaN(currentAbsoluteChild.style.dimensions[dim[axis]]) && currentAbsoluteChild.style.dimensions[dim[axis]] >= 0.0) &&
!float.IsNaN(currentAbsoluteChild.style.position[leading[axis]]) &&
!float.IsNaN(currentAbsoluteChild.style.position[trailing[axis]])) {
currentAbsoluteChild.layout.dimensions[dim[axis]] = Math.Max(
boundAxis(currentAbsoluteChild, axis, node.layout.dimensions[dim[axis]] -
(node.style.border.getWithFallback(leadingSpacing[axis], leading[axis]) + node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis])) -
(currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[axis], leading[axis]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[axis], trailing[axis])) -
(float.IsNaN(currentAbsoluteChild.style.position[leading[axis]]) ? 0 : currentAbsoluteChild.style.position[leading[axis]]) -
(float.IsNaN(currentAbsoluteChild.style.position[trailing[axis]]) ? 0 : currentAbsoluteChild.style.position[trailing[axis]])
),
// You never want to go smaller than padding
((currentAbsoluteChild.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + currentAbsoluteChild.style.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (currentAbsoluteChild.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + currentAbsoluteChild.style.border.getWithFallback(trailingSpacing[axis], trailing[axis])))
);
}
if (!float.IsNaN(currentAbsoluteChild.style.position[trailing[axis]]) &&
!!float.IsNaN(currentAbsoluteChild.style.position[leading[axis]])) {
currentAbsoluteChild.layout.position[leading[axis]] =
node.layout.dimensions[dim[axis]] -
currentAbsoluteChild.layout.dimensions[dim[axis]] -
(float.IsNaN(currentAbsoluteChild.style.position[trailing[axis]]) ? 0 : currentAbsoluteChild.style.position[trailing[axis]]);
}
}
child = currentAbsoluteChild;
currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild;
child.nextAbsoluteChild = null;
}
}
/** END_GENERATED **/
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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
{
/**
* POJO to hold the output of the measure function.
*/
public struct MeasureOutput
{
public MeasureOutput(float width, float height)
{
Width = width;
Height = height;
}
public readonly float Width;
public readonly float Height;
internal float width
{
get { return Width; }
}
internal float height
{
get { return Height; }
}
}
}

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.
*/
using System;
namespace Facebook.CSSLayout
{
/**
* This is here to preserve the @nullable attribute of the original Java API.
*/
[AttributeUsage(AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.Parameter)]
sealed class NullableAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,27 @@
/**
* 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.
*/
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("Facebook.CSSLayout")]
[assembly: AssemblyDescription("A subset of CSS's flexbox layout algorithm and box model.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Facebook")]
[assembly: AssemblyProduct("Facebook.CSSLayout")]
[assembly: AssemblyCopyright("Copyright © 2015 Facebook")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("Facebook.CSSLayout.Tests")]

View File

@@ -0,0 +1,234 @@
/**
* 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
{
/**
* Class representing CSS spacing (padding, margin, and borders). This is mostly necessary to
* properly implement interactions and updates for properties like margin, marginLeft, and
* marginHorizontal.
*/
sealed class Spacing
{
/**
* Spacing type that represents the left direction. E.g. {@code marginLeft}.
*/
internal const int LEFT = (int)CSSSpacingType.Left;
/**
* Spacing type that represents the top direction. E.g. {@code marginTop}.
*/
internal const int TOP = (int)CSSSpacingType.Top;
/**
* Spacing type that represents the right direction. E.g. {@code marginRight}.
*/
internal const int RIGHT = (int)CSSSpacingType.Right;
/**
* Spacing type that represents the bottom direction. E.g. {@code marginBottom}.
*/
internal const int BOTTOM = (int)CSSSpacingType.Bottom;
/**
* Spacing type that represents vertical direction (top and bottom). E.g. {@code marginVertical}.
*/
internal const int VERTICAL = (int)CSSSpacingType.Vertical;
/**
* Spacing type that represents horizontal direction (left and right). E.g.
* {@code marginHorizontal}.
*/
internal const int HORIZONTAL = (int)CSSSpacingType.Horizontal;
/**
* Spacing type that represents start direction e.g. left in left-to-right, right in right-to-left.
*/
internal const int START = (int)CSSSpacingType.Start;
/**
* Spacing type that represents end direction e.g. right in left-to-right, left in right-to-left.
*/
internal const int END = (int)CSSSpacingType.End;
/**
* Spacing type that represents all directions (left, top, right, bottom). E.g. {@code margin}.
*/
internal const int ALL = (int)CSSSpacingType.All;
static readonly int[] sFlagsMap = {
1, /*LEFT*/
2, /*TOP*/
4, /*RIGHT*/
8, /*BOTTOM*/
16, /*VERTICAL*/
32, /*HORIZONTAL*/
64, /*START*/
128, /*END*/
256 /*ALL*/
};
float[] mSpacing = newFullSpacingArray();
[Nullable] float[] mDefaultSpacing = null;
int mValueFlags = 0;
bool mHasAliasesSet;
/**
* Set a spacing value.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM},
* {@link #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
* @param value the value for this direction
* @return {@code true} if the spacing has changed, or {@code false} if the same value was already
* set
*/
internal bool set(int spacingType, float value)
{
if (!FloatUtil.floatsEqual(mSpacing[spacingType], value))
{
mSpacing[spacingType] = value;
if (CSSConstants.IsUndefined(value))
{
mValueFlags &= ~sFlagsMap[spacingType];
}
else
{
mValueFlags |= sFlagsMap[spacingType];
}
mHasAliasesSet =
(mValueFlags & sFlagsMap[ALL]) != 0 ||
(mValueFlags & sFlagsMap[VERTICAL]) != 0 ||
(mValueFlags & sFlagsMap[HORIZONTAL]) != 0;
return true;
}
return false;
}
/**
* Set a default spacing value. This is used as a fallback when no spacing has been set for a
* particular direction.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
* @param value the default value for this direction
* @return
*/
internal bool setDefault(int spacingType, float value)
{
if (mDefaultSpacing == null)
mDefaultSpacing = newSpacingResultArray();
if (!FloatUtil.floatsEqual(mDefaultSpacing[spacingType], value))
{
mDefaultSpacing[spacingType] = value;
return true;
}
return false;
}
/**
* Get the spacing for a direction. This takes into account any default values that have been set.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
*/
internal float get(int spacingType)
{
float defaultValue =
(mDefaultSpacing != null)
? mDefaultSpacing[spacingType]
: (spacingType == START || spacingType == END ? CSSConstants.Undefined : 0);
if (mValueFlags == 0)
{
return defaultValue;
}
if ((mValueFlags & sFlagsMap[spacingType]) != 0)
{
return mSpacing[spacingType];
}
if (mHasAliasesSet)
{
int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL;
if ((mValueFlags & sFlagsMap[secondType]) != 0)
{
return mSpacing[secondType];
}
else if ((mValueFlags & sFlagsMap[ALL]) != 0)
{
return mSpacing[ALL];
}
}
return defaultValue;
}
/**
* Get the raw value (that was set using {@link #set(int, float)}), without taking into account
* any default values.
*
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM},
* {@link #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
*/
internal float getRaw(int spacingType)
{
return mSpacing[spacingType];
}
/**
* Try to get start value and fallback to given type if not defined. This is used privately
* by the layout engine as a more efficient way to fetch direction-aware values by
* avoid extra method invocations.
*/
internal float getWithFallback(int spacingType, int fallbackType)
{
return
(mValueFlags & sFlagsMap[spacingType]) != 0
? mSpacing[spacingType]
: get(fallbackType);
}
static float[] newFullSpacingArray()
{
return new[]
{
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined,
CSSConstants.Undefined
};
}
static float[] newSpacingResultArray()
{
return newSpacingResultArray(0);
}
static float[] newSpacingResultArray(float defaultValue)
{
return new[]
{
defaultValue,
defaultValue,
defaultValue,
defaultValue,
defaultValue,
defaultValue,
CSSConstants.Undefined,
CSSConstants.Undefined,
defaultValue
};
}
}
}

38
src/csharp/Makefile Normal file
View File

@@ -0,0 +1,38 @@
MSB=msbuild.exe /m /verbosity:m /nologo
NUGET=nuget.exe
NUNITC=nunit-console.exe
VER=1.0.0
NAME=Facebook.CSSLayout
.PHONY: all
all: test
.PHONY: distribute
distribute: package release-package
.PHONY: package
package: conf=Release
package: build
cd ${NAME} && ${NUGET} pack ${NAME}.csproj -Version ${VER} -Prop Configuration=${conf}
.PHONY: release-package
release-package:
cd ${NAME} && nuget push ${NAME}.${VER}.nupkg
.PHONY: test
test: build-debug
cd ${NAME}.Tests/bin/Debug && ${NUNITC} Facebook.CSSLayout.Tests.dll
.PHONY: build-debug
build-debug: conf=Debug
build-debug: build
.PHONY: build-release
build-release: conf=Release
build-release: build
.PHONY: build
build:
${MSB} ${NAME}.sln /p:Configuration=${conf} /t:"Facebook_CSSLayout:Rebuild;Facebook_CSSLayout_Tests:Rebuild"

View File

@@ -3,7 +3,7 @@
//
// This file uses the following specific UMD implementation:
// https://github.com/umdjs/umd/blob/master/returnExports.js
(function (root, factory) {
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
@@ -16,11 +16,14 @@
// Browser globals (root is window)
root.computeLayout = factory();
}
}(this, function () {
// @@include('./Layout.js')
}(this, function() {
// @@include('./Layout.js')
return function(node) {
computeLayout.fillNodes(node);
computeLayout.computeLayout(node);
return function(node) {
/*eslint-disable */
// disabling ESLint because this code relies on the above include
computeLayout.fillNodes(node);
computeLayout.computeLayout(node);
/*eslint-enable */
};
}));

View File

@@ -8,49 +8,50 @@
*/
package com.facebook.csslayout;
import java.util.Arrays;
/**
* Where the output of {@link LayoutEngine#layoutNode(CSSNode, float)} will go in the CSSNode.
*/
public class CSSLayout {
public static final int POSITION_LEFT = 0;
public static final int POSITION_TOP = 1;
public static final int POSITION_RIGHT = 2;
public static final int POSITION_BOTTOM = 3;
public float top;
public float left;
public float right;
public float bottom;
public float width = CSSConstants.UNDEFINED;
public float height = CSSConstants.UNDEFINED;
public static final int DIMENSION_WIDTH = 0;
public static final int DIMENSION_HEIGHT = 1;
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 void resetResult() {
left = 0;
top = 0;
right = 0;
bottom = 0;
width = CSSConstants.UNDEFINED;
height = CSSConstants.UNDEFINED;
Arrays.fill(position, 0);
Arrays.fill(dimensions, CSSConstants.UNDEFINED);
direction = CSSDirection.LTR;
}
public void copy(CSSLayout layout) {
left = layout.left;
top = layout.top;
right = layout.right;
bottom = layout.bottom;
width = layout.width;
height = layout.height;
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;
}
@Override
public String toString() {
return "layout: {" +
"left: " + left + ", " +
"top: " + top + ", " +
"width: " + width + ", " +
"height: " + height + ", " +
"left: " + position[POSITION_LEFT] + ", " +
"top: " + position[POSITION_TOP] + ", " +
"width: " + dimensions[DIMENSION_WIDTH] + ", " +
"height: " + dimensions[DIMENSION_HEIGHT] + ", " +
"direction: " + direction +
"}";
}

View File

@@ -14,6 +14,13 @@ import java.util.ArrayList;
import com.facebook.infer.annotation.Assertions;
import static com.facebook.csslayout.CSSLayout.DIMENSION_HEIGHT;
import static com.facebook.csslayout.CSSLayout.DIMENSION_WIDTH;
import static com.facebook.csslayout.CSSLayout.POSITION_BOTTOM;
import static com.facebook.csslayout.CSSLayout.POSITION_LEFT;
import static com.facebook.csslayout.CSSLayout.POSITION_RIGHT;
import static com.facebook.csslayout.CSSLayout.POSITION_TOP;
/**
* A CSS Node. It has a style object you can manipulate at {@link #style}. After calling
* {@link #calculateLayout()}, {@link #layout} will be filled with the results of the layout.
@@ -56,6 +63,9 @@ public class CSSNode {
public int lineIndex = 0;
/*package*/ CSSNode nextAbsoluteChild;
/*package*/ CSSNode nextFlexChild;
private @Nullable ArrayList<CSSNode> mChildren;
private @Nullable CSSNode mParent;
private @Nullable MeasureFunction mMeasureFunction = null;
@@ -105,7 +115,7 @@ public class CSSNode {
}
public void setMeasureFunction(MeasureFunction measureFunction) {
if (!valuesEqual(mMeasureFunction, measureFunction)) {
if (mMeasureFunction != measureFunction) {
mMeasureFunction = measureFunction;
dirty();
}
@@ -211,62 +221,104 @@ public class CSSNode {
return FloatUtil.floatsEqual(f1, f2);
}
protected <T> boolean valuesEqual(@Nullable T o1, @Nullable T o2) {
if (o1 == null) {
return o2 == null;
}
return o1.equals(o2);
/**
* Get this node's direction, as defined in the style.
*/
public CSSDirection getStyleDirection() {
return style.direction;
}
public void setDirection(CSSDirection direction) {
if (!valuesEqual(style.direction, direction)) {
if (style.direction != direction) {
style.direction = direction;
dirty();
}
}
/**
* Get this node's flex direction, as defined by style.
*/
public CSSFlexDirection getFlexDirection() {
return style.flexDirection;
}
public void setFlexDirection(CSSFlexDirection flexDirection) {
if (!valuesEqual(style.flexDirection, flexDirection)) {
if (style.flexDirection != flexDirection) {
style.flexDirection = flexDirection;
dirty();
}
}
/**
* Get this node's justify content, as defined by style.
*/
public CSSJustify getJustifyContent() {
return style.justifyContent;
}
public void setJustifyContent(CSSJustify justifyContent) {
if (!valuesEqual(style.justifyContent, justifyContent)) {
if (style.justifyContent != justifyContent) {
style.justifyContent = justifyContent;
dirty();
}
}
/**
* Get this node's align items, as defined by style.
*/
public CSSAlign getAlignItems() {
return style.alignItems;
}
public void setAlignItems(CSSAlign alignItems) {
if (!valuesEqual(style.alignItems, alignItems)) {
if (style.alignItems != alignItems) {
style.alignItems = alignItems;
dirty();
}
}
/**
* Get this node's align items, as defined by style.
*/
public CSSAlign getAlignSelf() {
return style.alignSelf;
}
public void setAlignSelf(CSSAlign alignSelf) {
if (!valuesEqual(style.alignSelf, alignSelf)) {
if (style.alignSelf != alignSelf) {
style.alignSelf = alignSelf;
dirty();
}
}
/**
* Get this node's position type, as defined by style.
*/
public CSSPositionType getPositionType() {
return style.positionType;
}
public void setPositionType(CSSPositionType positionType) {
if (!valuesEqual(style.positionType, positionType)) {
if (style.positionType != positionType) {
style.positionType = positionType;
dirty();
}
}
public void setWrap(CSSWrap flexWrap) {
if (!valuesEqual(style.flexWrap, flexWrap)) {
if (style.flexWrap != flexWrap) {
style.flexWrap = flexWrap;
dirty();
}
}
/**
* Get this node's flex, as defined by style.
*/
public float getFlex() {
return style.flex;
}
public void setFlex(float flex) {
if (!valuesEqual(style.flex, flex)) {
style.flex = flex;
@@ -274,112 +326,147 @@ public class CSSNode {
}
}
/**
* Get this node's margin, as defined by style + default margin.
*/
public Spacing getMargin() {
return style.margin;
}
public void setMargin(int spacingType, float margin) {
if (style.margin.set(spacingType, margin)) {
dirty();
}
}
/**
* Get this node's padding, as defined by style + default padding.
*/
public Spacing getPadding() {
return style.padding;
}
public void setPadding(int spacingType, float padding) {
if (style.padding.set(spacingType, padding)) {
dirty();
}
}
/**
* Get this node's border, as defined by style.
*/
public Spacing getBorder() {
return style.border;
}
public void setBorder(int spacingType, float border) {
if (style.border.set(spacingType, border)) {
dirty();
}
}
/**
* Get this node's position top, as defined by style.
*/
public float getPositionTop() {
return style.position[POSITION_TOP];
}
public void setPositionTop(float positionTop) {
if (!valuesEqual(style.positionTop, positionTop)) {
style.positionTop = positionTop;
if (!valuesEqual(style.position[POSITION_TOP], positionTop)) {
style.position[POSITION_TOP] = positionTop;
dirty();
}
}
public void setPositionBottom(float positionBottom) {
if (!valuesEqual(style.positionBottom, positionBottom)) {
style.positionBottom = positionBottom;
dirty();
}
}
public void setPositionLeft(float positionLeft) {
if (!valuesEqual(style.positionLeft, positionLeft)) {
style.positionLeft = positionLeft;
dirty();
}
}
public void setPositionRight(float positionRight) {
if (!valuesEqual(style.positionRight, positionRight)) {
style.positionRight = positionRight;
dirty();
}
}
public void setStyleWidth(float width) {
if (!valuesEqual(style.width, width)) {
style.width = width;
dirty();
}
}
public void setStyleHeight(float height) {
if (!valuesEqual(style.height, height)) {
style.height = height;
dirty();
}
}
public float getLayoutX() {
return layout.left;
}
public float getLayoutY() {
return layout.top;
}
public float getLayoutWidth() {
return layout.width;
}
public float getLayoutHeight() {
return layout.height;
}
public CSSDirection getLayoutDirection() {
return layout.direction;
}
/**
* Get this node's padding, as defined by style + default padding.
* Get this node's position bottom, as defined by style.
*/
public Spacing getStylePadding() {
return style.padding;
public float getPositionBottom() {
return style.position[POSITION_BOTTOM];
}
public void setPositionBottom(float positionBottom) {
if (!valuesEqual(style.position[POSITION_BOTTOM], positionBottom)) {
style.position[POSITION_BOTTOM] = positionBottom;
dirty();
}
}
/**
* Get this node's position left, as defined by style.
*/
public float getPositionLeft() {
return style.position[POSITION_LEFT];
}
public void setPositionLeft(float positionLeft) {
if (!valuesEqual(style.position[POSITION_LEFT], positionLeft)) {
style.position[POSITION_LEFT] = positionLeft;
dirty();
}
}
/**
* Get this node's position right, as defined by style.
*/
public float getPositionRight() {
return style.position[POSITION_RIGHT];
}
public void setPositionRight(float positionRight) {
if (!valuesEqual(style.position[POSITION_RIGHT], positionRight)) {
style.position[POSITION_RIGHT] = positionRight;
dirty();
}
}
/**
* Get this node's width, as defined in the style.
*/
public float getStyleWidth() {
return style.width;
return style.dimensions[DIMENSION_WIDTH];
}
public void setStyleWidth(float width) {
if (!valuesEqual(style.dimensions[DIMENSION_WIDTH], width)) {
style.dimensions[DIMENSION_WIDTH] = width;
dirty();
}
}
/**
* Get this node's height, as defined in the style.
*/
public float getStyleHeight() {
return style.height;
return style.dimensions[DIMENSION_HEIGHT];
}
/**
* Get this node's direction, as defined in the style.
*/
public CSSDirection getStyleDirection() {
return style.direction;
public void setStyleHeight(float height) {
if (!valuesEqual(style.dimensions[DIMENSION_HEIGHT], height)) {
style.dimensions[DIMENSION_HEIGHT] = height;
dirty();
}
}
public float getLayoutX() {
return layout.position[POSITION_LEFT];
}
public float getLayoutY() {
return layout.position[POSITION_TOP];
}
public float getLayoutWidth() {
return layout.dimensions[DIMENSION_WIDTH];
}
public float getLayoutHeight() {
return layout.dimensions[DIMENSION_HEIGHT];
}
public CSSDirection getLayoutDirection() {
return layout.direction;
}
/**
@@ -390,4 +477,19 @@ public class CSSNode {
dirty();
}
}
/**
* Resets this instance to its default state. This method is meant to be used when
* recycling {@link CSSNode} instances.
*/
public void reset() {
if (mParent != null || (mChildren != null && mChildren.size() > 0)) {
throw new IllegalStateException("You should not reset an attached CSSNode");
}
style.reset();
layout.resetResult();
lineIndex = 0;
mLayoutState = LayoutState.DIRTY;
}
}

View File

@@ -8,36 +8,62 @@
*/
package com.facebook.csslayout;
import java.util.Arrays;
/**
* The CSS style definition for a {@link CSSNode}.
*/
public class CSSStyle {
public CSSDirection direction = CSSDirection.INHERIT;
public CSSFlexDirection flexDirection = CSSFlexDirection.COLUMN;
public CSSJustify justifyContent = CSSJustify.FLEX_START;
public CSSAlign alignContent = CSSAlign.FLEX_START;
public CSSAlign alignItems = CSSAlign.STRETCH;
public CSSAlign alignSelf = CSSAlign.AUTO;
public CSSPositionType positionType = CSSPositionType.RELATIVE;
public CSSWrap flexWrap = CSSWrap.NOWRAP;
public CSSDirection direction;
public CSSFlexDirection flexDirection;
public CSSJustify justifyContent;
public CSSAlign alignContent;
public CSSAlign alignItems;
public CSSAlign alignSelf;
public CSSPositionType positionType;
public CSSWrap flexWrap;
public float flex;
public Spacing margin = new Spacing();
public Spacing padding = new Spacing();
public Spacing border = new Spacing();
public float positionTop = CSSConstants.UNDEFINED;
public float positionBottom = CSSConstants.UNDEFINED;
public float positionLeft = CSSConstants.UNDEFINED;
public float positionRight = CSSConstants.UNDEFINED;
public float width = CSSConstants.UNDEFINED;
public float height = CSSConstants.UNDEFINED;
public float[] position = new float[4];
public float[] dimensions = new float[2];
public float minWidth = CSSConstants.UNDEFINED;
public float minHeight = CSSConstants.UNDEFINED;
public float maxWidth = CSSConstants.UNDEFINED;
public float maxHeight = CSSConstants.UNDEFINED;
CSSStyle() {
reset();
}
void reset() {
direction = CSSDirection.INHERIT;
flexDirection = CSSFlexDirection.COLUMN;
justifyContent = CSSJustify.FLEX_START;
alignContent = CSSAlign.FLEX_START;
alignItems = CSSAlign.STRETCH;
alignSelf = CSSAlign.AUTO;
positionType = CSSPositionType.RELATIVE;
flexWrap = CSSWrap.NOWRAP;
flex = 0f;
margin.reset();;
padding.reset();
border.reset();
Arrays.fill(position, CSSConstants.UNDEFINED);
Arrays.fill(dimensions, CSSConstants.UNDEFINED);
minWidth = CSSConstants.UNDEFINED;
minHeight = CSSConstants.UNDEFINED;
maxWidth = CSSConstants.UNDEFINED;
maxHeight = CSSConstants.UNDEFINED;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@ package com.facebook.csslayout;
import javax.annotation.Nullable;
import java.util.Arrays;
/**
* Class representing CSS spacing (padding, margin, and borders). This is mostly necessary to
* properly implement interactions and updates for properties like margin, marginLeft, and
@@ -55,8 +57,22 @@ public class Spacing {
*/
public static final int ALL = 8;
private static final int[] sFlagsMap = {
1, /*LEFT*/
2, /*TOP*/
4, /*RIGHT*/
8, /*BOTTOM*/
16, /*VERTICAL*/
32, /*HORIZONTAL*/
64, /*START*/
128, /*END*/
256, /*ALL*/
};
private final float[] mSpacing = newFullSpacingArray();
@Nullable private float[] mDefaultSpacing = null;
private int mValueFlags = 0;
private boolean mHasAliasesSet;
/**
* Set a spacing value.
@@ -70,6 +86,18 @@ public class Spacing {
public boolean set(int spacingType, float value) {
if (!FloatUtil.floatsEqual(mSpacing[spacingType], value)) {
mSpacing[spacingType] = value;
if (CSSConstants.isUndefined(value)) {
mValueFlags &= ~sFlagsMap[spacingType];
} else {
mValueFlags |= sFlagsMap[spacingType];
}
mHasAliasesSet =
(mValueFlags & sFlagsMap[ALL]) != 0 ||
(mValueFlags & sFlagsMap[VERTICAL]) != 0 ||
(mValueFlags & sFlagsMap[HORIZONTAL]) != 0;
return true;
}
return false;
@@ -100,18 +128,28 @@ public class Spacing {
* @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
*/
public float get(int spacingType) {
int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL;
float defaultValue = spacingType == START || spacingType == END ? CSSConstants.UNDEFINED : 0;
return
!CSSConstants.isUndefined(mSpacing[spacingType])
? mSpacing[spacingType]
: !CSSConstants.isUndefined(mSpacing[secondType])
? mSpacing[secondType]
: !CSSConstants.isUndefined(mSpacing[ALL])
? mSpacing[ALL]
: mDefaultSpacing != null
? mDefaultSpacing[spacingType]
: defaultValue;
float defaultValue = (mDefaultSpacing != null)
? mDefaultSpacing[spacingType]
: (spacingType == START || spacingType == END ? CSSConstants.UNDEFINED : 0);
if (mValueFlags == 0) {
return defaultValue;
}
if ((mValueFlags & sFlagsMap[spacingType]) != 0) {
return mSpacing[spacingType];
}
if (mHasAliasesSet) {
int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL;
if ((mValueFlags & sFlagsMap[secondType]) != 0) {
return mSpacing[secondType];
} else if ((mValueFlags & sFlagsMap[ALL]) != 0) {
return mSpacing[ALL];
}
}
return defaultValue;
}
/**
@@ -125,6 +163,29 @@ public class Spacing {
return mSpacing[spacingType];
}
/**
* Resets the spacing instance to its default state. This method is meant to be used when
* recycling {@link Spacing} instances.
*/
void reset() {
Arrays.fill(mSpacing, CSSConstants.UNDEFINED);
mDefaultSpacing = null;
mHasAliasesSet = false;
mValueFlags = 0;
}
/**
* Try to get start value and fallback to given type if not defined. This is used privately
* by the layout engine as a more efficient way to fetch direction-aware values by
* avoid extra method invocations.
*/
float getWithFallback(int spacingType, int fallbackType) {
return
(mValueFlags & sFlagsMap[spacingType]) != 0
? mSpacing[spacingType]
: get(fallbackType);
}
private static float[] newFullSpacingArray() {
return new float[] {
CSSConstants.UNDEFINED,

File diff suppressed because it is too large Load Diff

View File

@@ -8,9 +8,10 @@
*/
var layoutTestUtils = require('./Layout-test-utils.js');
var computeLayout = require('./Layout.js').computeLayout;
var computeLayout = require('./Layout.js').layoutNodeImpl;
var fs = require('fs');
var JavaTranspiler = require('./JavaTranspiler.js');
var CSharpTranspiler = require('./CSharpTranspiler.js');
var currentTest = '';
var allTests = [];
@@ -243,6 +244,7 @@ function printLayout(test) {
function transpileAnnotatedJStoC(jsCode) {
return jsCode
.replace('node.style.measure', 'node.measure')
.replace(/null/g, 'NULL')
.replace(/\.children\.length/g, '.children_count')
.replace(/\.width/g, '.dimensions[CSS_WIDTH]')
.replace(/\.height/g, '.dimensions[CSS_HEIGHT]')
@@ -251,6 +253,8 @@ 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(/layout\[pos/g, 'layout.position[pos')
.replace(/layout\[leading/g, 'layout.position[leading')
@@ -261,6 +265,12 @@ function transpileAnnotatedJStoC(jsCode) {
.replace(/node\./g, 'node->')
.replace(/child\./g, 'child->')
.replace(/parent\./g, 'parent->')
.replace(/currentAbsoluteChild\./g, 'currentAbsoluteChild->')
.replace(/currentFlexChild\./g, 'currentFlexChild->')
.replace(/getPositionType\((.+?)\)/g, '$1->style.position_type')
.replace(/getJustifyContent\((.+?)\)/g, '$1->style.justify_content')
.replace(/getAlignContent\((.+?)\)/g, '$1->style.align_content')
.replace(/var\/\*\(c\)!([^*]+)\*\//g, '$1')
.replace(/var\/\*([^\/]+)\*\//g, '$1')
.replace(/ === /g, ' == ')
.replace(/ !== /g, ' != ')
@@ -301,3 +311,6 @@ generateFile(__dirname + '/Layout.c', transpileAnnotatedJStoC(computeLayout.toSt
generateFile(__dirname + '/java/src/com/facebook/csslayout/LayoutEngine.java', JavaTranspiler.transpileLayoutEngine(computeLayout.toString()));
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.Tests/TestConstants.cs', CSharpTranspiler.transpileCConstDefs(makeConstDefs()));
generateFile(__dirname + '/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs', CSharpTranspiler.transpileCTestsArray(allTestsInC));