Skip to content

Instantly share code, notes, and snippets.

@qoh
Last active March 25, 2016 16:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save qoh/331a395b8d8e2c4c0723 to your computer and use it in GitHub Desktop.
Save qoh/331a395b8d8e2c4c0723 to your computer and use it in GitHub Desktop.
// 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