Merge pull request #129 from pragmatrix/csharp-fb-pr

C# Transpiler, API, and Tests
This commit is contained in:
Christopher Chedeau
2015-09-23 09:08:15 -07:00
35 changed files with 11051 additions and 0 deletions

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;
}

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,505 @@
/**
* 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;
using static Facebook.CSSLayout.CSSLayout;
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
{
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
=> mChildren.Count;
public CSSNode this[int i]
=> mChildren[i];
public IEnumerable<CSSNode> Children
=> 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
=> 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
=> mLayoutState == LayoutState.DIRTY;
/**
* See {@link LayoutState#HAS_NEW_LAYOUT}.
*/
public bool HasNewLayout
=> 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_HEIGHT], 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 => layout.position[POSITION_LEFT];
public float LayoutY => layout.position[POSITION_TOP];
public float LayoutWidth => layout.dimensions[DIMENSION_WIDTH];
public float LayoutHeight => layout.dimensions[DIMENSION_HEIGHT];
public CSSDirection LayoutDirection => 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,69 @@
<?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>
</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,928 @@
/**
* 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;
using static Facebook.CSSLayout.CSSLayout;
namespace Facebook.CSSLayout
{
/**
* Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float)}.
*/
static class LayoutEngine
{
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) {
CSSAlign alignItem = getAlignItem(node, child);
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,29 @@
/**
* 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 => Width;
internal float height => 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

@@ -11,6 +11,7 @@ var layoutTestUtils = require('./Layout-test-utils.js');
var computeLayout = require('./Layout.js').computeLayout;
var fs = require('fs');
var JavaTranspiler = require('./JavaTranspiler.js');
var CSharpTranspiler = require('./CSharpTranspiler.js');
var currentTest = '';
var allTests = [];
@@ -310,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));