Last active
March 25, 2016 16:27
-
-
Save qoh/331a395b8d8e2c4c0723 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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. | |
$CSS_DIRECTION_INHERIT = "inherit"; | |
$CSS_DIRECTION_LTR = "ltr"; | |
$CSS_DIRECTION_RTL = "rtl"; | |
$CSS_FLEX_DIRECTION_ROW = "row"; | |
$CSS_FLEX_DIRECTION_ROW_REVERSE = "row-reverse"; | |
$CSS_FLEX_DIRECTION_COLUMN = "column"; | |
$CSS_FLEX_DIRECTION_COLUMN_REVERSE = "column-reverse"; | |
$CSS_JUSTIFY_FLEX_START = "flex-start"; | |
$CSS_JUSTIFY_CENTER = "center"; | |
$CSS_JUSTIFY_FLEX_END = "flex-end"; | |
$CSS_JUSTIFY_SPACE_BETWEEN = "space-between"; | |
$CSS_JUSTIFY_SPACE_AROUND = "space-around"; | |
$CSS_ALIGN_FLEX_START = "flex-start"; | |
$CSS_ALIGN_CENTER = "center"; | |
$CSS_ALIGN_FLEX_END = "flex-end"; | |
$CSS_ALIGN_STRETCH = "stretch"; | |
$CSS_POSITION_RELATIVE = "relative"; | |
$CSS_POSITION_ABSOLUTE = "absolute"; | |
$leading["row"] = "left"; | |
$leading["row-reverse"] = "right"; | |
$leading["column"] = "top"; | |
$leading["column-reverse"] = "bottom"; | |
$trailing["row"] = "right"; | |
$trailing["row-reverse"] = "left"; | |
$trailing["column"] = "bottom"; | |
$trailing["column-reverse"] = "top"; | |
$pos["row"] = "left"; | |
$pos["row-reverse"] = "right"; | |
$pos["column"] = "top"; | |
$pos["column-reverse"] = "bottom"; | |
$dim["row"] = "width"; | |
$dim["row-reverse"] = "width"; | |
$dim["column"] = "height"; | |
$dim["column-reverse"] = "height"; | |
// When transpiled to Java / C the node type has layout, children and style | |
// properties. For the JavaScript version this function adds these properties | |
// if they don"t already exist. | |
function fillNodes(%node) { | |
if (%node.isDirty) { | |
%node.layout["width"] = ""; | |
%node.layout["height"] = ""; | |
%node.layout["top"] = 0; | |
%node.layout["left"] = 0; | |
%node.layout["right"] = 0; | |
%node.layout["bottom"] = 0; | |
} | |
for (%i = %node.getCount() - 1; %i >= 0; %i--) { | |
fillNodes(%node.getObject(%i)); | |
} | |
} | |
function isRowDirection(%flexDirection) { | |
return %flexDirection $= $CSS_FLEX_DIRECTION_ROW || | |
%flexDirection $= $CSS_FLEX_DIRECTION_ROW_REVERSE; | |
} | |
function isColumnDirection(%flexDirection) { | |
return %flexDirection $= $CSS_FLEX_DIRECTION_COLUMN || | |
%flexDirection $= $CSS_FLEX_DIRECTION_COLUMN_REVERSE; | |
} | |
function getLeadingMargin(%node, %axis) { | |
if (%node.style["marginStart"] !$= "" && isRowDirection(%axis)) { | |
return %node.style["marginStart"]; | |
} | |
switch$ (%axis) { | |
case "row": %value = %node.style["marginLeft"]; | |
case "row-reverse": %value = %node.style["marginRight"]; | |
case "column": %value = %node.style["marginTop"]; | |
case "column-reverse": %value = %node.style["marginBottom"]; | |
} | |
if (%value !$= "") { | |
return %value; | |
} | |
if (%node.style["margin"] !$= "") { | |
return %node.style["margin"]; | |
} | |
return 0; | |
} | |
function getTrailingMargin(%node, %axis) { | |
if (%node.style["marginEnd"] !$= "" && isRowDirection(%axis)) { | |
return %node.style["marginEnd"]; | |
} | |
switch$ (%axis) { | |
case "row": %value = %node.style["marginRight"]; | |
case "row-reverse": %value = %node.style["marginLeft"]; | |
case "column": %value = %node.style["marginBottom"]; | |
case "column-reverse": %value = %node.style["marginTop"]; | |
} | |
if (%value !$= "") { | |
return %value; | |
} | |
if (%node.style["margin"] !$= "") { | |
return %node.style["margin"]; | |
} | |
return 0; | |
} | |
function getLeadingPadding(%node, %axis) { | |
if (%node.style["paddingStart"] !$= "" && %node.style["paddingStart"] >= 0 | |
&& isRowDirection(%axis)) { | |
return %node.style["paddingStart"]; | |
} | |
switch$ (%axis) { | |
case "row": %value = %node.style["paddingLeft"]; | |
case "row-reverse": %value = %node.style["paddingRight"]; | |
case "column": %value = %node.style["paddingTop"]; | |
case "column-reverse": %value = %node.style["paddingBottom"]; | |
} | |
if (%value !$= "" && %value >= 0) { | |
return %value; | |
} | |
if (%node.style["padding"] !$= "" && %node.style["padding"] >= 0) { | |
return %node.style["padding"]; | |
} | |
return 0; | |
} | |
function getTrailingPadding(%node, %axis) { | |
if (%node.style["paddingEnd"] !$= "" && %node.style["paddingEnd"] >= 0 | |
&& isRowDirection(%axis)) { | |
return %node.style["paddingEnd"]; | |
} | |
switch$ (%axis) { | |
case "row": %value = %node.style["paddingRight"]; | |
case "row-reverse": %value = %node.style["paddingLeft"]; | |
case "column": %value = %node.style["paddingBottom"]; | |
case "column-reverse": %value = %node.style["paddingTop"]; | |
} | |
if (%value !$= "" && %value >= 0) { | |
return %value; | |
} | |
if (%node.style["padding"] !$= "" && %node.style["padding"] >= 0) { | |
return %node.style["padding"]; | |
} | |
return 0; | |
} | |
function getLeadingBorder(%node, %axis) { | |
if (%node.style["borderStartWidth"] !$= "" && %node.style["borderStartWidth"] >= 0 | |
&& isRowDirection(%axis)) { | |
return %node.style["borderStartWidth"]; | |
} | |
switch$ (%axis) { | |
case "row": %value = %node.style["borderLeftWidth"]; | |
case "row-reverse": %value = %node.style["borderRightWidth"]; | |
case "column": %value = %node.style["borderTopWidth"]; | |
case "column-reverse": %value = %node.style["borderBottomWidth"]; | |
} | |
if (%value !$= "" && %value >= 0) { | |
return %value; | |
} | |
if (%node.style["borderWidth"] !$= "" && %node.style["borderWidth"] >= 0) { | |
return %node.style["borderWidth"]; | |
} | |
return 0; | |
} | |
function getTrailingBorder(%node, %axis) { | |
if (%node.style["borderEndWidth"] !$= "" && %node.style["borderEndWidth"] >= 0 | |
&& isRowDirection(%axis)) { | |
return %node.style["borderEndWidth"]; | |
} | |
switch$ (%axis) { | |
case "row": %value = %node.style["borderRightWidth"]; | |
case "row-reverse": %value = %node.style["borderLeftWidth"]; | |
case "column": %value = %node.style["borderBottomWidth"]; | |
case "column-reverse": %value = %node.style["borderTopWidth"]; | |
} | |
if (%value !$= "" && %value >= 0) { | |
return %value; | |
} | |
if (%node.style["borderWidth"] !$= "" && %node.style["borderWidth"] >= 0) { | |
return %node.style["borderWidth"]; | |
} | |
return 0; | |
} | |
function getLeadingPaddingAndBorder(%node, %axis) { | |
return getLeadingPadding(%node, %axis) + getLeadingBorder(%node, %axis); | |
} | |
function getTrailingPaddingAndBorder(%node, %axis) { | |
return getTrailingPadding(%node, %axis) + getTrailingBorder(%node, %axis); | |
} | |
function getBorderAxis(%node, %axis) { | |
return getLeadingBorder(%node, %axis) + getTrailingBorder(%node, %axis); | |
} | |
function getMarginAxis(%node, %axis) { | |
return getLeadingMargin(%node, %axis) + getTrailingMargin(%node, %axis); | |
} | |
function getPaddingAndBorderAxis(%node, %axis) { | |
return getLeadingPaddingAndBorder(%node, %axis) + | |
getTrailingPaddingAndBorder(%node, %axis); | |
} | |
function getJustifyContent(%node) { | |
if (%node.style["justifyContent"]) { | |
return %node.style["justifyContent"]; | |
} | |
return "flex-start"; | |
} | |
function getAlignContent(%node) { | |
if (%node.style["alignContent"]) { | |
return %node.style["alignContent"]; | |
} | |
return "flex-start"; | |
} | |
function getAlignItem(%node, %child) { | |
if (%child.style["alignSelf"]) { | |
return %child.style["alignSelf"]; | |
} | |
if (%node.style["alignItems"]) { | |
return %node.style["alignItems"]; | |
} | |
return "stretch"; | |
} | |
function resolveAxis(%axis, %direction) { | |
if (%direction $= $CSS_DIRECTION_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; | |
} | |
function resolveDirection(%node, %parentDirection) { | |
if (%node.style["direction"]) { | |
%direction = %node.style["direction"]; | |
} else { | |
%direction = $CSS_DIRECTION_INHERIT; | |
} | |
if (%direction $= $CSS_DIRECTION_INHERIT) { | |
%direction = (%parentDirection $= "" ? $CSS_DIRECTION_LTR : %parentDirection); | |
} | |
return %direction; | |
} | |
function getFlexDirection(%node) { | |
if (%node.style["flexDirection"]) { | |
return %node.style["flexDirection"]; | |
} | |
return $CSS_FLEX_DIRECTION_COLUMN; | |
} | |
function getCrossFlexDirection(%flexDirection, %direction) { | |
if (isColumnDirection(%flexDirection)) { | |
return resolveAxis($CSS_FLEX_DIRECTION_ROW, %direction); | |
} else { | |
return $CSS_FLEX_DIRECTION_COLUMN; | |
} | |
} | |
function getPositionType(%node) { | |
if (%node.style["position"]) { | |
return %node.style["position"]; | |
} | |
return "relative"; | |
} | |
function isFlex(%node) { | |
return ( | |
getPositionType(%node) $= $CSS_POSITION_RELATIVE && | |
%node.style["flex"] > 0 | |
); | |
} | |
function isFlexWrap(%node) { | |
return %node.style["flexWrap"] $= "wrap"; | |
} | |
function getDimWithMargin(%node, %axis) { | |
return %node.layout[$dim[%axis]] + getMarginAxis(%node, %axis); | |
} | |
function isStyleDimDefined(%node, %axis) { | |
return %node.style[$dim[%axis]] !$= "" && %node.style[$dim[%axis]] >= 0; | |
} | |
function isLayoutDimDefined(%node, %axis) { | |
return %node.layout[$dim[%axis]] !$= "" && %node.layout[$dim[%axis]] >= 0; | |
} | |
function isPosDefined(%node, %pos) { | |
return %node.style[%pos] !$= ""; | |
} | |
function isMeasureDefined(%node) { | |
return %node.style["measure"] !$= ""; | |
} | |
function getPosition(%node, %pos) { | |
if (%node.style[%pos] !$= "") { | |
return %node.style[%pos]; | |
} | |
return 0; | |
} | |
function boundAxis(%node, %axis, %value) { | |
%_min["row"] = %node.style["minWidth"]; | |
%_min["row-reverse"] = %node.style["minWidth"]; | |
%_min["column"] = %node.style["minHeight"]; | |
%_min["column-reverse"] = %node.style["minHeight"]; | |
%min = %_min[%axis]; | |
%_max["row"] = %node.style["maxWidth"]; | |
%_max["row-reverse"] = %node.style["maxWidth"]; | |
%_max["column"] = %node.style["maxHeight"]; | |
%_max["column-reverse"] = %node.style["maxHeight"]; | |
%max = %_max[%axis]; | |
%boundValue = %value; | |
if (%max !$= "" && %max >= 0 && %boundValue > %max) { | |
%boundValue = %max; | |
} | |
if (%min !$= "" && %min >= 0 && %boundValue < %min) { | |
%boundValue = %min; | |
} | |
return %boundValue; | |
} | |
function fmaxf(%a, %b) { | |
if (%a > %b) { | |
return %a; | |
} | |
return %b; | |
} | |
// When the user specifically sets a value for width or height | |
function setDimensionFromStyle(%node, %axis) { | |
// The parent already computed us a width or height. We just skip it | |
if (isLayoutDimDefined(%node, %axis)) { | |
return; | |
} | |
// We only run if there"s a width or height defined | |
if (!isStyleDimDefined(%node, %axis)) { | |
return; | |
} | |
// The dimensions can never be smaller than the padding and border | |
%node.layout[$dim[%axis]] = fmaxf( | |
boundAxis(%node, %axis, %node.style[$dim[%axis]]), | |
getPaddingAndBorderAxis(%node, %axis) | |
); | |
} | |
function setTrailingPosition(%node, %child, %axis) { | |
%child.layout[$trailing[%axis]] = %node.layout[$dim[%axis]] - | |
%child.layout[$dim[%axis]] - %child.layout[$pos[%axis]]; | |
} | |
// If both left and right are defined, then use left. Otherwise return | |
// +left or -right depending on which is defined. | |
function getRelativePosition(%node, %axis) { | |
if (%node.style[$leading[%axis]] !$= "") { | |
return getPosition(%node, $leading[%axis]); | |
} | |
return -getPosition(%node, $trailing[%axis]); | |
} | |
function layoutNodeImpl(%node, %parentMaxWidth, %parentMaxHeight, %parentDirection) { | |
%direction = resolveDirection(%node, %parentDirection); | |
%mainAxis = resolveAxis(getFlexDirection(%node), %direction); | |
%crossAxis = getCrossFlexDirection(%mainAxis, %direction); | |
%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[$leading[%mainAxis]] += getLeadingMargin(%node, %mainAxis) + | |
getRelativePosition(%node, %mainAxis); | |
%node.layout[$trailing[%mainAxis]] += getTrailingMargin(%node, %mainAxis) + | |
getRelativePosition(%node, %mainAxis); | |
%node.layout[$leading[%crossAxis]] += getLeadingMargin(%node, %crossAxis) + | |
getRelativePosition(%node, %crossAxis); | |
%node.layout[$trailing[%crossAxis]] += getTrailingMargin(%node, %crossAxis) + | |
getRelativePosition(%node, %crossAxis); | |
// Inline immutable values from the target %node to avoid excessive method | |
// invocations during the layout calculation. | |
%childCount = %node.getCount(); | |
%paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(%node, %resolvedRowAxis); | |
%paddingAndBorderAxisColumn = getPaddingAndBorderAxis(%node, $CSS_FLEX_DIRECTION_COLUMN); | |
if (isMeasureDefined(%node)) { | |
%isResolvedRowDimDefined = isLayoutDimDefined(%node, %resolvedRowAxis); | |
if (isStyleDimDefined(%node, %resolvedRowAxis)) { | |
%width = %node.style["width"]; | |
} else if (%isResolvedRowDimDefined) { | |
%width = %node.layout[$dim[%resolvedRowAxis]]; | |
} else { | |
%width = %parentMaxWidth - | |
getMarginAxis(%node, %resolvedRowAxis); | |
} | |
%width -= %paddingAndBorderAxisResolvedRow; | |
%height = ""; | |
if (isStyleDimDefined(%node, $CSS_FLEX_DIRECTION_COLUMN)) { | |
%height = %node.style["height"]; | |
} else if (isLayoutDimDefined(%node, $CSS_FLEX_DIRECTION_COLUMN)) { | |
%height = %node.layout[$dim[CSS_FLEX_DIRECTION_COLUMN]]; | |
} else { | |
%height = %parentMaxHeight - | |
getMarginAxis(%node, %resolvedRowAxis); | |
} | |
%height -= getPaddingAndBorderAxis(%node, $CSS_FLEX_DIRECTION_COLUMN); | |
// 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. | |
%isRowUndefined = !isStyleDimDefined(%node, %resolvedRowAxis) && !%isResolvedRowDimDefined; | |
%isColumnUndefined = !isStyleDimDefined(%node, $CSS_FLEX_DIRECTION_COLUMN) && | |
(%node.layout[$dim[$CSS_FLEX_DIRECTION_COLUMN]] $= ""); | |
// Let"s not measure the text if we already know both dimensions | |
if (%isRowUndefined || %isColumnUndefined) { | |
%measureDim = %node.layoutMeasure( | |
%width, | |
%height | |
); | |
if (%isRowUndefined) { | |
%node.layout["width"] = getWord(%measureDim, 0) + | |
%paddingAndBorderAxisResolvedRow; | |
} | |
if (%isColumnUndefined) { | |
%node.layout["height"] = getWord(%measureDim, 1) + | |
%paddingAndBorderAxisColumn; | |
} | |
} | |
if (%childCount == 0) { | |
return; | |
} | |
} | |
%isNodeFlexWrap = isFlexWrap(%node); | |
%justifyContent = getJustifyContent(%node); | |
%leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(%node, %mainAxis); | |
%leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(%node, %crossAxis); | |
%paddingAndBorderAxisMain = getPaddingAndBorderAxis(%node, %mainAxis); | |
%paddingAndBorderAxisCross = getPaddingAndBorderAxis(%node, %crossAxis); | |
%isMainDimDefined = isLayoutDimDefined(%node, %mainAxis); | |
%isCrossDimDefined = isLayoutDimDefined(%node, %crossAxis); | |
%isMainRowDirection = isRowDirection(%mainAxis); | |
%definedMainDim = ""; | |
if (%isMainDimDefined) { | |
%definedMainDim = %node.layout[$dim[%mainAxis]] - %paddingAndBorderAxisMain; | |
} | |
// We want to execute the next two loops one per line with flex-wrap | |
%startLine = 0; | |
%endLine = 0; | |
// %nextOffset = 0; | |
%alreadyComputedNextLayout = 0; | |
// We aggregate the total dimensions of the container in those two variables | |
%linesCrossDim = 0; | |
%linesMainDim = 0; | |
%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. | |
%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. | |
%flexibleChildrenCount = 0; | |
%totalFlexible = 0; | |
%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>. | |
%isSimpleStackMain = | |
(%isMainDimDefined && %justifyContent $= $CSS_JUSTIFY_FLEX_START) || | |
(!%isMainDimDefined && %justifyContent !$= $CSS_JUSTIFY_CENTER); | |
%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>. | |
%isSimpleStackCross = true; | |
%firstComplexCross = %childCount; | |
%firstFlexChild = ""; | |
%currentFlexChild = ""; | |
%mainDim = %leadingPaddingAndBorderMain; | |
%crossDim = 0; | |
%maxWidth = ""; | |
%maxHeight = ""; | |
for (%i = %startLine; %i < %childCount; %i++) { | |
%child = %node.getObject(%i); | |
%child.lineIndex = %linesCount; | |
%child.nextAbsoluteChild = ""; | |
%child.nextFlexChild = ""; | |
%alignItem = getAlignItem(%node, %child); | |
// Pre-fill cross axis dimensions when the child is using stretch before | |
// we call the recursive layout pass | |
if (%alignItem $= $CSS_ALIGN_STRETCH && | |
getPositionType(%child) $= $CSS_POSITION_RELATIVE && | |
%isCrossDimDefined && | |
!isStyleDimDefined(%child, %crossAxis)) { | |
%child.layout[$dim[%crossAxis]] = fmaxf( | |
boundAxis(%child, %crossAxis, %node.layout[$dim[%crossAxis]] - | |
%paddingAndBorderAxisCross - getMarginAxis(%child, %crossAxis)), | |
// You never want to go smaller than padding | |
getPaddingAndBorderAxis(%child, %crossAxis) | |
); | |
} else if (getPositionType(%child) $= $CSS_POSITION_ABSOLUTE) { | |
// Store a private linked list of absolutely positioned children | |
// so that we can efficiently traverse them later. | |
if (%firstAbsoluteChild $= "") { | |
%firstAbsoluteChild = %child; | |
} | |
if (%currentAbsoluteChild !$= "") { | |
%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 (isLayoutDimDefined(%node, %axis) && | |
!isStyleDimDefined(%child, %axis) && | |
isPosDefined(%child, $leading[%axis]) && | |
isPosDefined(%child, $trailing[%axis])) { | |
%child.layout[$dim[%axis]] = fmaxf( | |
boundAxis(%child, %axis, %node.layout[$dim[%axis]] - | |
getPaddingAndBorderAxis(%node, %axis) - | |
getMarginAxis(%child, %axis) - | |
getPosition(%child, $leading[%axis]) - | |
getPosition(%child, $trailing[%axis])), | |
// You never want to go smaller than padding | |
getPaddingAndBorderAxis(%child, %axis) | |
); | |
} | |
} | |
} | |
%nextContentDim = 0; | |
// It only makes sense to consider a child flexible if we have a computed | |
// dimension for the %node. | |
if (%isMainDimDefined && isFlex(%child)) { | |
%flexibleChildrenCount++; | |
%totalFlexible += %child.style["flex"]; | |
// Store a private linked list of flexible children so that we can | |
// efficiently traverse them later. | |
if (%firstFlexChild $= "") { | |
%firstFlexChild = child; | |
} | |
if (%currentFlexChild !$= "") { | |
%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 = getPaddingAndBorderAxis(%child, %mainAxis) + | |
getMarginAxis(%child, %mainAxis); | |
} else { | |
%maxWidth = ""; | |
%maxHeight = ""; | |
if (!%isMainRowDirection) { | |
if (isLayoutDimDefined(%node, %resolvedRowAxis)) { | |
%maxWidth = %node.layout[$dim[%resolvedRowAxis]] - | |
%paddingAndBorderAxisResolvedRow; | |
} else { | |
%maxWidth = %parentMaxWidth - | |
getMarginAxis(%node, %resolvedRowAxis) - | |
%paddingAndBorderAxisResolvedRow; | |
} | |
} else { | |
if (isLayoutDimDefined(%node, $CSS_FLEX_DIRECTION_COLUMN)) { | |
%maxHeight = %node.layout[$dim[$CSS_FLEX_DIRECTION_COLUMN]] - | |
%paddingAndBorderAxisColumn; | |
} else { | |
%maxHeight = %parentMaxHeight - | |
getMarginAxis(%node, $CSS_FLEX_DIRECTION_COLUMN) - | |
%paddingAndBorderAxisColumn; | |
} | |
} | |
// This is the main recursive call. We layout non flexible children. | |
if (%alreadyComputedNextLayout $= 0) { | |
layoutNode(%child, %maxWidth, %maxHeight, %direction); | |
} | |
// Absolute positioned elements do not take part of the layout, so we | |
// don"t use them to compute mainContentDim | |
if (getPositionType(%child) $= $CSS_POSITION_RELATIVE) { | |
%nonFlexibleChildrenCount++; | |
// At this point we know the final size and margin of the element. | |
%nextContentDim = getDimWithMargin(%child, %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 && | |
(getPositionType(%child) !$= $CSS_POSITION_RELATIVE || isFlex(%child))) { | |
%isSimpleStackMain = false; | |
%firstComplexMain = %i; | |
} | |
// Disable simple stacking in the cross axis for the current line as | |
// we found a non-trivial child. The remaining children will be laid out | |
// in <Loop D>. | |
if (%isSimpleStackCross && | |
(getPositionType(%child) !$= $CSS_POSITION_RELATIVE || | |
(%alignItem !$= $CSS_ALIGN_STRETCH && %alignItem !$= $CSS_ALIGN_FLEX_START) || | |
(%alignItem $= $CSS_ALIGN_STRETCH && !%isCrossDimDefined))) { | |
%isSimpleStackCross = false; | |
%firstComplexCross = %i; | |
} | |
if (%isSimpleStackMain) { | |
%child.layout[$pos[%mainAxis]] += %mainDim; | |
if (%isMainDimDefined) { | |
setTrailingPosition(%node, %child, %mainAxis); | |
} | |
%mainDim += getDimWithMargin(%child, %mainAxis); | |
%crossDim = fmaxf(%crossDim, boundAxis(%child, %crossAxis, getDimWithMargin(%child, %crossAxis))); | |
} | |
if (%isSimpleStackCross) { | |
%child.layout[$pos[%crossAxis]] += %linesCrossDim + %leadingPaddingAndBorderCross; | |
if (%isCrossDimDefined) { | |
setTrailingPosition(%node, %child, %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. | |
%leadingMainDim = 0; | |
%betweenMainDim = 0; | |
// The remaining available space that needs to be allocated | |
%remainingMainDim = 0; | |
if (%isMainDimDefined) { | |
%remainingMainDim = %definedMainDim - %mainContentDim; | |
} else { | |
%remainingMainDim = fmaxf(%mainContentDim, 0) - %mainContentDim; | |
} | |
// If there are flexible children in the mix, they are going to fill the | |
// remaining space | |
if (%flexibleChildrenCount !$= 0) { | |
%flexibleMainDim = %remainingMainDim / %totalFlexible; | |
// If the flex share of remaining space doesn"t meet min/max bounds, | |
// remove this child from flex calculations. | |
%currentFlexChild = %firstFlexChild; | |
while (%currentFlexChild !$= "") { | |
%baseMainDim = %flexibleMainDim * %currentFlexChild.style["flex"] + | |
getPaddingAndBorderAxis(%currentFlexChild, %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 !$= "") { | |
// At this point we know the final size of the element in the main | |
// dimension | |
%currentFlexChild.layout[$dim[%mainAxis]] = boundAxis(%currentFlexChild, %mainAxis, | |
%flexibleMainDim * %currentFlexChild.style["flex"] + | |
getPaddingAndBorderAxis(%currentFlexChild, %mainAxis) | |
); | |
%maxWidth = ""; | |
if (isLayoutDimDefined(%node, %resolvedRowAxis)) { | |
%maxWidth = %node.layout[$dim[%resolvedRowAxis]] - | |
%paddingAndBorderAxisResolvedRow; | |
} else if (!%isMainRowDirection) { | |
%maxWidth = %parentMaxWidth - | |
getMarginAxis(%node, %resolvedRowAxis) - | |
%paddingAndBorderAxisResolvedRow; | |
} | |
%maxHeight = ""; | |
if (isLayoutDimDefined(%node, $CSS_FLEX_DIRECTION_COLUMN)) { | |
%maxHeight = %node.layout[$dim[$CSS_FLEX_DIRECTION_COLUMN]] - | |
%paddingAndBorderAxisColumn; | |
} else if (%isMainRowDirection) { | |
%maxHeight = %parentMaxHeight - | |
getMarginAxis(%node, $CSS_FLEX_DIRECTION_COLUMN) - | |
%paddingAndBorderAxisColumn; | |
} | |
// And we recursively call the layout algorithm for this child | |
layoutNode(%currentFlexChild, %maxWidth, %maxHeight, %direction); | |
%child = %currentFlexChild; | |
%currentFlexChild = %currentFlexChild.nextFlexChild; | |
%child.nextFlexChild = ""; | |
} | |
// We use justifyContent to figure out how to allocate the remaining | |
// space available | |
} else if (%justifyContent !$= $CSS_JUSTIFY_FLEX_START) { | |
if (%justifyContent $= $CSS_JUSTIFY_CENTER) { | |
%leadingMainDim = %remainingMainDim / 2; | |
} else if (%justifyContent $= $CSS_JUSTIFY_FLEX_END) { | |
%leadingMainDim = %remainingMainDim; | |
} else if (%justifyContent $= $CSS_JUSTIFY_SPACE_BETWEEN) { | |
%remainingMainDim = fmaxf(%remainingMainDim, 0); | |
if (%flexibleChildrenCount + %nonFlexibleChildrenCount - 1 !$= 0) { | |
%betweenMainDim = %remainingMainDim / | |
(%flexibleChildrenCount + %nonFlexibleChildrenCount - 1); | |
} else { | |
%betweenMainDim = 0; | |
} | |
} else if (%justifyContent $= $CSS_JUSTIFY_SPACE_AROUND) { | |
// 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.getObject(%i); | |
if (getPositionType(%child) $= $CSS_POSITION_ABSOLUTE && | |
isPosDefined(%child, $leading[%mainAxis])) { | |
// In case the child is position absolute and has left/top being | |
// defined, we override the position to whatever the user said | |
// (and margin/border). | |
%child.layout[$pos[%mainAxis]] = getPosition(%child, $leading[%mainAxis]) + | |
getLeadingBorder(%node, %mainAxis) + | |
getLeadingMargin(%child, %mainAxis); | |
} else { | |
// If the child is position absolute (without top/left) or relative, | |
// we put it at the current accumulated offset. | |
%child.layout[$pos[%mainAxis]] += %mainDim; | |
// Define the trailing position accordingly. | |
if (%isMainDimDefined) { | |
setTrailingPosition(%node, %child, %mainAxis); | |
} | |
// Now that we placed the element, we need to update the variables | |
// We only need to do that for relative elements. Absolute elements | |
// do not take part in that phase. | |
if (getPositionType(%child) $= $CSS_POSITION_RELATIVE) { | |
// The main dimension is the sum of all the elements dimension plus | |
// the spacing. | |
%mainDim += %betweenMainDim + getDimWithMargin(%child, %mainAxis); | |
// The cross dimension is the max of the elements dimension since there | |
// can only be one element in that cross dimension. | |
%crossDim = fmaxf(%crossDim, boundAxis(%child, %crossAxis, getDimWithMargin(%child, %crossAxis))); | |
} | |
} | |
} | |
%containerCrossAxis = %node.layout[$dim[%crossAxis]]; | |
if (!%isCrossDimDefined) { | |
%containerCrossAxis = fmaxf( | |
// For the cross dim, we add both sides at the end because the value | |
// is aggregate via a max function. Intermediate negative values | |
// can mess this computation otherwise | |
boundAxis(%node, %crossAxis, %crossDim + %paddingAndBorderAxisCross), | |
%paddingAndBorderAxisCross | |
); | |
} | |
// <Loop D> Position elements in the cross axis | |
for (%i = %firstComplexCross; %i < %endLine; %i++) { | |
%child = %node.getObject(%i); | |
if (getPositionType(%child) $= $CSS_POSITION_ABSOLUTE && | |
isPosDefined(%child, $leading[%crossAxis])) { | |
// In case the child is absolutely positionned and has a | |
// top/left/bottom/right being set, we override all the previously | |
// computed positions to set it correctly. | |
%child.layout[$pos[%crossAxis]] = getPosition(%child, $leading[%crossAxis]) + | |
getLeadingBorder(%node, %crossAxis) + | |
getLeadingMargin(%child, %crossAxis); | |
} else { | |
%leadingCrossDim = %leadingPaddingAndBorderCross; | |
// For a relative children, we"re either using alignItems (parent) or | |
// alignSelf (child) in order to determine the position in the cross axis | |
if (getPositionType(%child) $= $CSS_POSITION_RELATIVE) { | |
// This variable is intentionally re-defined as the code is transpiled to a block scope language | |
%alignItem = getAlignItem(%node, %child); | |
if (%alignItem $= $CSS_ALIGN_STRETCH) { | |
// You can only stretch if the dimension has not already been defined | |
// previously. | |
if (!isStyleDimDefined(%child, %crossAxis)) { | |
%dimCrossAxis = %child.layout[$dim[%crossAxis]]; | |
%child.layout[$dim[%crossAxis]] = fmaxf( | |
boundAxis(%child, %crossAxis, %containerCrossAxis - | |
%paddingAndBorderAxisCross - getMarginAxis(%child, %crossAxis)), | |
// You never want to go smaller than padding | |
getPaddingAndBorderAxis(%child, %crossAxis) | |
); | |
// If the size has changed, and this child has children we need to re-layout this child | |
if (%dimCrossAxis !$= %child.layout[$dim[%crossAxis]] && %child.children.length > 0) { | |
// Reset child margins before re-layout as they are added back in layoutNode and would be doubled | |
%child.layout[$leading[%mainAxis]] -= getLeadingMargin(%child, %mainAxis) + | |
getRelativePosition(%child, %mainAxis); | |
%child.layout[$trailing[%mainAxis]] -= getTrailingMargin(%child, %mainAxis) + | |
getRelativePosition(%child, %mainAxis); | |
%child.layout[$leading[%crossAxis]] -= getLeadingMargin(%child, %crossAxis) + | |
getRelativePosition(%child, %crossAxis); | |
%child.layout[$trailing[%crossAxis]] -= getTrailingMargin(%child, %crossAxis) + | |
getRelativePosition(%child, %crossAxis); | |
layoutNode(%child, %maxWidth, %maxHeight, %direction); | |
} | |
} | |
} else if (%alignItem !$= $CSS_ALIGN_FLEX_START) { | |
// The remaining space between the parent dimensions+padding and child | |
// dimensions+margin. | |
%remainingCrossDim = %containerCrossAxis - | |
%paddingAndBorderAxisCross - getDimWithMargin(%child, %crossAxis); | |
if (%alignItem $= $CSS_ALIGN_CENTER) { | |
%leadingCrossDim += %remainingCrossDim / 2; | |
} else { // $CSS_ALIGN_FLEX_END | |
%leadingCrossDim += %remainingCrossDim; | |
} | |
} | |
} | |
// And we apply the position | |
%child.layout[$pos[%crossAxis]] += %linesCrossDim + %leadingCrossDim; | |
// Define the trailing position accordingly. | |
if (%isCrossDimDefined) { | |
setTrailingPosition(%node, %child, %crossAxis); | |
} | |
} | |
} | |
%linesCrossDim += %crossDim; | |
%linesMainDim = fmaxf(%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) { | |
%nodeCrossAxisInnerSize = %node.layout[$dim[%crossAxis]] - | |
%paddingAndBorderAxisCross; | |
%remainingAlignContentDim = %nodeCrossAxisInnerSize - %linesCrossDim; | |
%crossDimLead = 0; | |
%currentLead = %leadingPaddingAndBorderCross; | |
%alignContent = getAlignContent(%node); | |
if (%alignContent $= $CSS_ALIGN_FLEX_END) { | |
%currentLead += %remainingAlignContentDim; | |
} else if (%alignContent $= $CSS_ALIGN_CENTER) { | |
%currentLead += %remainingAlignContentDim / 2; | |
} else if (%alignContent $= $CSS_ALIGN_STRETCH) { | |
if (%nodeCrossAxisInnerSize > %linesCrossDim) { | |
%crossDimLead = (%remainingAlignContentDim / %linesCount); | |
} | |
} | |
%endIndex = 0; | |
for (%i = 0; %i < %linesCount; %i++) { | |
%startIndex = %endIndex; | |
// compute the line"s height and find the endIndex | |
%lineHeight = 0; | |
for (%ii = %startIndex; %ii < %childCount; %ii++) { | |
%child = %node.getObject(%ii); | |
if (getPositionType(%child) !$= $CSS_POSITION_RELATIVE) { | |
continue; | |
} | |
if (%child.lineIndex != %i) { | |
break; | |
} | |
if (isLayoutDimDefined(%child, %crossAxis)) { | |
%lineHeight = fmaxf( | |
%lineHeight, | |
%child.layout[$dim[%crossAxis]] + getMarginAxis(%child, %crossAxis) | |
); | |
} | |
} | |
%endIndex = %ii; | |
%lineHeight += %crossDimLead; | |
for (%ii = %startIndex; %ii < %endIndex; %ii++) { | |
%child = %node.getObject(%ii); | |
if (getPositionType(%child) !$= $CSS_POSITION_RELATIVE) { | |
continue; | |
} | |
%alignContentAlignItem = getAlignItem(%node, %child); | |
if (%alignContentAlignItem $= $CSS_ALIGN_FLEX_START) { | |
%child.layout[$pos[%crossAxis]] = %currentLead + getLeadingMargin(%child, %crossAxis); | |
} else if (%alignContentAlignItem $= $CSS_ALIGN_FLEX_END) { | |
%child.layout[$pos[%crossAxis]] = %currentLead + %lineHeight - getTrailingMargin(%child, %crossAxis) - %child.layout[$dim[%crossAxis]]; | |
} else if (%alignContentAlignItem $= $CSS_ALIGN_CENTER) { | |
%childHeight = %child.layout[$dim[%crossAxis]]; | |
%child.layout[$pos[%crossAxis]] = %currentLead + (%lineHeight - %childHeight) / 2; | |
} else if (%alignContentAlignItem $= $CSS_ALIGN_STRETCH) { | |
%child.layout[$pos[%crossAxis]] = %currentLead + getLeadingMargin(%child, %crossAxis); | |
// TODO(prenaux): Correctly set the height of items with undefined | |
// (auto) crossAxis dimension. | |
} | |
} | |
%currentLead += %lineHeight; | |
} | |
} | |
%needsMainTrailingPos = false; | |
%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[$dim[%mainAxis]] = fmaxf( | |
// We"re missing the last padding at this point to get the final | |
// dimension | |
boundAxis(%node, %mainAxis, %linesMainDim + getTrailingPaddingAndBorder(%node, %mainAxis)), | |
// We can never assign a width smaller than the padding and borders | |
%paddingAndBorderAxisMain | |
); | |
if (%mainAxis $= $CSS_FLEX_DIRECTION_ROW_REVERSE || | |
%mainAxis $= $CSS_FLEX_DIRECTION_COLUMN_REVERSE) { | |
%needsMainTrailingPos = true; | |
} | |
} | |
if (!%isCrossDimDefined) { | |
%node.layout[$dim[%crossAxis]] = fmaxf( | |
// For the cross dim, we add both sides at the end because the value | |
// is aggregate via a max function. Intermediate negative values | |
// can mess this computation otherwise | |
boundAxis(%node, %crossAxis, %linesCrossDim + %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.getObject(%i); | |
if (%needsMainTrailingPos) { | |
setTrailingPosition(%node, %child, %mainAxis); | |
} | |
if (%needsCrossTrailingPos) { | |
setTrailingPosition(%node, %child, %crossAxis); | |
} | |
} | |
} | |
// <Loop G> Calculate dimensions for absolutely positioned elements | |
%currentAbsoluteChild = %firstAbsoluteChild; | |
while (%currentAbsoluteChild !$= "") { | |
// 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 (isLayoutDimDefined(%node, %axis) && | |
!isStyleDimDefined(%currentAbsoluteChild, %axis) && | |
isPosDefined(%currentAbsoluteChild, $leading[%axis]) && | |
isPosDefined(%currentAbsoluteChild, $trailing[%axis])) { | |
%currentAbsoluteChild.layout[$dim[%axis]] = fmaxf( | |
boundAxis(%currentAbsoluteChild, %axis, %node.layout[$dim[%axis]] - | |
getBorderAxis(%node, %axis) - | |
getMarginAxis(%currentAbsoluteChild, %axis) - | |
getPosition(%currentAbsoluteChild, $leading[%axis]) - | |
getPosition(%currentAbsoluteChild, $trailing[%axis]) | |
), | |
// You never want to go smaller than padding | |
getPaddingAndBorderAxis(%currentAbsoluteChild, %axis) | |
); | |
} | |
if (isPosDefined(%currentAbsoluteChild, $trailing[%axis]) && | |
!isPosDefined(%currentAbsoluteChild, $leading[%axis])) { | |
%currentAbsoluteChild.layout[$leading[%axis]] = | |
%node.layout[$dim[%axis]] - | |
%currentAbsoluteChild.layout[$dim[%axis]] - | |
getPosition(%currentAbsoluteChild, $trailing[%axis]); | |
} | |
} | |
%child = %currentAbsoluteChild; | |
%currentAbsoluteChild = %currentAbsoluteChild.nextAbsoluteChild; | |
%child.nextAbsoluteChild = ""; | |
} | |
} | |
function layoutNode(%node, %parentMaxWidth, %parentMaxHeight, %parentDirection) { | |
%node.shouldUpdate = true; | |
%direction = %node.style["direction"] || $CSS_DIRECTION_LTR; | |
%skipLayout = | |
!%node.isDirty && | |
%node.lastLayout && | |
%node.lastLayout["requestedHeight"] $= %node.layout["height"] && | |
%node.lastLayout["requestedWidth"] $= %node.layout["width"] && | |
%node.lastLayout["parentMaxWidth"] $= %parentMaxWidth && | |
%node.lastLayout["parentMaxHeight"] $= %parentMaxHeight && | |
%node.lastLayout["direction"] $= direction; | |
if (%skipLayout) { | |
%node.layout["width"] = %node.lastLayout["width"]; | |
%node.layout["height"] = %node.lastLayout["height"]; | |
%node.layout["top"] = %node.lastLayout["top"]; | |
%node.layout["left"] = %node.lastLayout["left"]; | |
} else { | |
%node.lastLayout["requestedWidth"] = %node.layout["width"]; | |
%node.lastLayout["requestedHeight"] = %node.layout["height"]; | |
%node.lastLayout["parentMaxWidth"] = %parentMaxWidth; | |
%node.lastLayout["parentMaxHeight"] = %parentMaxHeight; | |
%node.lastLayout["direction"] = direction; | |
// Reset child layouts | |
for (%i = %node.getCount() - 1; %i >= 0; %i--) { | |
%child = %node.getObject(%i); | |
%child.layout["width"] = ""; | |
%child.layout["height"] = ""; | |
%child.layout["top"] = 0; | |
%child.layout["left"] = 0; | |
} | |
layoutNodeImpl(%node, %parentMaxWidth, %parentMaxHeight, %parentDirection); | |
%node.lastLayout["width"] = %node.layout["width"]; | |
%node.lastLayout["height"] = %node.layout["height"]; | |
%node.lastLayout["top"] = %node.layout["top"]; | |
%node.lastLayout["left"] = %node.layout["left"]; | |
} | |
} | |
function computeLayout(%node) { | |
fillNodes(%node); | |
layoutNode(%node); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment