/** * 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. */ /* * From css-layout comments: * The spec describes four different layout modes: "fill available", "max * content", "min content", and "fit content". Of these, we don't use * "min content" because we don't support default minimum main sizes (see * above for details). Each of our measure modes maps to a layout mode * from the spec (https://www.w3.org/TR/css3-sizing/#terms): * * -.CssMeasureModeUndefined: `max-content` * -.CssMeasureModeExactly: `fill-available` * -.CssMeasureModeAtMost: `fit-content` * If infinite space available in that axis, then `max-content.` * Else, `min(max-content size, max(min-content size, fill-available size))` * (Although, we don't support min-content) */ open LayoutTypes; open LayoutValue; open LayoutSupport; let gCurrentGenerationCount = ref 0; let gDepth = ref 0; let gPrintTree = {contents: false}; let gPrintChanges = {contents: false}; let gPrintSkips = {contents: false}; let measureString = "measure"; let stretchString = "stretch"; let absMeasureString = "abs-measure"; let absLayoutString = "abs-layout"; let initialString = "initial"; let flexString = "flex"; let spacer = " "; let getSpacer level => { let spacerLen = String.length spacer; let lvl = level > spacerLen ? level : spacerLen; String.sub spacer lvl (String.length spacer) }; let getModeName (mode, isLayoutInsteadOfMeasure) => switch mode { | CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS => isLayoutInsteadOfMeasure ? "CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS" : "CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS" | CssMeasureModeUndefined => isLayoutInsteadOfMeasure ? "LAY_UNDEFINED" : "UNDEFINED" | CssMeasureModeExactly => isLayoutInsteadOfMeasure ? "LAY_EXACTLY" : "EXACTLY" | CssMeasureModeAtMost => isLayoutInsteadOfMeasure ? "LAY_AT_MOST" : "AT_MOST" }; let canUseCachedMeasurement ( availableWidth, availableHeight, marginRow, marginColumn, widthMeasureMode, heightMeasureMode, cachedLayout ) => if ( cachedLayout.availableWidth == availableWidth && cachedLayout.availableHeight == availableHeight && cachedLayout.widthMeasureMode == widthMeasureMode && cachedLayout.heightMeasureMode == heightMeasureMode ) { true } else if /* Is it an exact match?*/ /* If the width is an exact match, try a fuzzy match on the height.*/ ( cachedLayout.widthMeasureMode == widthMeasureMode && cachedLayout.availableWidth == availableWidth && heightMeasureMode === CssMeasureModeExactly && availableHeight -. marginColumn == cachedLayout.computedHeight ) { true } else if /* If the height is an exact match, try a fuzzy match on the width.*/ ( cachedLayout.heightMeasureMode == heightMeasureMode && cachedLayout.availableHeight == availableHeight && widthMeasureMode === CssMeasureModeExactly && availableWidth -. marginRow == cachedLayout.computedWidth ) { true } else { false }; let cachedMeasurementAt layout i => switch i { | 0 => layout.cachedMeasurement1 | 1 => layout.cachedMeasurement2 | 2 => layout.cachedMeasurement3 | 3 => layout.cachedMeasurement4 | 4 => layout.cachedMeasurement5 | 5 => layout.cachedMeasurement6 | _ => raise (Invalid_argument ("No cached measurement at " ^ string_of_int i)) }; /** * This is a wrapper around the layoutNodeImpl function. It determines * whether the layout request is redundant and can be skipped. * * Parameters: * Input parameters are the same as layoutNodeImpl (see above) * Return parameter is true if layout was performed, false if skipped */ let rec layoutNodeInternal node availableWidth availableHeight parentDirection widthMeasureMode heightMeasureMode performLayout reason => { let layout = node.layout; gDepth.contents = gDepth.contents + 1; let needToVisitNode = node.isDirty node.context && layout.generationCount != gCurrentGenerationCount.contents || layout.lastParentDirection != parentDirection; if needToVisitNode { /* Invalidate the cached results.*/ layout.nextCachedMeasurementsIndex = 0; layout.cachedLayout.widthMeasureMode = CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS; layout.cachedLayout.heightMeasureMode = CSS_MEASURE_MODE_NEGATIVE_ONE_WHATEVER_THAT_MEANS }; let cachedResults = ref None; /* Determine whether the results are already cached. We maintain a separate*/ /* cache for layouts and measurements. A layout operation modifies the positions*/ /* and dimensions for nodes in the subtree. The algorithm assumes that each node*/ /* gets layed out a maximum of one time per tree layout, but multiple measurements*/ /* may be required to resolve all of the flex dimensions.*/ /* We handle nodes with measure functions specially here because they are the most * expensive to measure, so it's worth avoiding redundant measurements if at all possible.*/ if (node.measure !== dummyMeasure && node.childrenCount === 0) { let marginAxisRow = getMarginAxis node CssFlexDirectionRow; let marginAxisColumn = getMarginAxis node CssFlexDirectionColumn; /* First, try to use the layout cache.*/ if ( canUseCachedMeasurement ( availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout.cachedLayout ) ) { cachedResults.contents = Some layout.cachedLayout } else { /* Try to use the measurement cache.*/ let foundCached = {contents: false}; for i in 0 to (layout.nextCachedMeasurementsIndex - 1) { /* This is basically the "break" */ if (not foundCached.contents) { let cachedMeasurementAtIndex = cachedMeasurementAt layout i; if ( canUseCachedMeasurement ( availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, cachedMeasurementAtIndex ) ) { cachedResults.contents = Some cachedMeasurementAtIndex; foundCached.contents = true } } } } } else if performLayout { if ( layout.cachedLayout.availableWidth == availableWidth && layout.cachedLayout.availableHeight == availableHeight && layout.cachedLayout.widthMeasureMode == widthMeasureMode && layout.cachedLayout.heightMeasureMode == heightMeasureMode ) { cachedResults.contents = Some layout.cachedLayout } } else { let foundCached = {contents: false}; for i in 0 to (layout.nextCachedMeasurementsIndex - 1) { /* This is basically the "break" */ if (not foundCached.contents) { let cachedMeasurementAtIndex = cachedMeasurementAt layout i; if ( cachedMeasurementAtIndex.availableWidth == availableWidth && cachedMeasurementAtIndex.availableHeight == availableHeight && cachedMeasurementAtIndex.widthMeasureMode == widthMeasureMode && cachedMeasurementAtIndex.heightMeasureMode == heightMeasureMode ) { cachedResults.contents = Some cachedMeasurementAtIndex; foundCached.contents = true } } } }; if (not needToVisitNode && cachedResults.contents != None) { let cachedResults_ = switch cachedResults.contents { | None => raise (Invalid_argument "Not possible") | Some cr => cr }; layout.measuredWidth = cachedResults_.computedWidth; layout.measuredHeight = cachedResults_.computedHeight; if (gPrintChanges.contents && gPrintSkips.contents) { Printf.printf "%s%d.{[skipped] " (getSpacer gDepth.contents) gDepth.contents; switch node.print { | None => () | Some printer => printer node.context }; Printf.printf "wm: %s, hm: %s, aw: %s ah: %s => d: (%s, %s) %s\n" (getModeName (widthMeasureMode, performLayout)) (getModeName (heightMeasureMode, performLayout)) (scalarToString availableWidth) (scalarToString availableHeight) (scalarToString cachedResults_.computedWidth) (scalarToString cachedResults_.computedHeight) reason } } else { if gPrintChanges.contents { Printf.printf "%s%d.{%s" (getSpacer gDepth.contents) gDepth.contents (needToVisitNode ? "*" : ""); switch node.print { | None => () | Some printer => printer node.context }; Printf.printf "wm: %s, hm: %s, aw: %s ah: %s %s\n" (getModeName (widthMeasureMode, performLayout)) (getModeName (heightMeasureMode, performLayout)) (scalarToString availableWidth) (scalarToString availableHeight) reason }; layoutNodeImpl ( node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout ); if gPrintChanges.contents { Printf.printf "%s%d.}%s" (getSpacer gDepth.contents) gDepth.contents (needToVisitNode ? "*" : ""); switch node.print { | None => () | Some printer => printer node.context }; Printf.printf "wm: %s, hm: %s, d: (%s, %s) %s\n" (getModeName (widthMeasureMode, performLayout)) (getModeName (heightMeasureMode, performLayout)) (scalarToString layout.measuredWidth) (scalarToString layout.measuredHeight) reason }; layout.lastParentDirection = parentDirection; if (cachedResults.contents === None) { if (layout.nextCachedMeasurementsIndex == css_max_cached_result_count) { if gPrintChanges.contents { Printf.printf "Out of cache entries!\n" }; layout.nextCachedMeasurementsIndex = 0 }; let newCacheEntry = performLayout ? /* Use the single layout cache entry.*/ layout.cachedLayout : { /* Allocate a new measurement cache entry.*/ let newCacheEntry_ = cachedMeasurementAt layout layout.nextCachedMeasurementsIndex; layout.nextCachedMeasurementsIndex = layout.nextCachedMeasurementsIndex + 1; newCacheEntry_ }; newCacheEntry.availableWidth = availableWidth; newCacheEntry.availableHeight = availableHeight; newCacheEntry.widthMeasureMode = widthMeasureMode; newCacheEntry.heightMeasureMode = heightMeasureMode; newCacheEntry.computedWidth = layout.measuredWidth; newCacheEntry.computedHeight = layout.measuredHeight } }; if performLayout { node.layout.width = node.layout.measuredWidth; node.layout.height = node.layout.measuredHeight; layout.hasNewLayout = true }; gDepth.contents = gDepth.contents - 1; layout.generationCount = gCurrentGenerationCount.contents; needToVisitNode || cachedResults.contents === None } and computeChildFlexBasis node child width widthMode height heightMode direction => { let mainAxis = resolveAxis node.style.flexDirection direction; let isMainAxisRow = isRowDirection mainAxis; let childWidth = {contents: zero}; let childHeight = {contents: zero}; let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; if (isMainAxisRow && isStyleDimDefined child.contents CssFlexDirectionRow) { child.contents.layout.computedFlexBasis = fmaxf child.contents.style.width (getPaddingAndBorderAxis child.contents CssFlexDirectionRow) } else if ( not isMainAxisRow && isStyleDimDefined child.contents CssFlexDirectionColumn ) { child.contents.layout.computedFlexBasis = fmaxf child.contents.style.height (getPaddingAndBorderAxis child.contents CssFlexDirectionColumn) } else if ( not (isUndefined child.contents.style.flexBasis) ) { if (isUndefined child.contents.layout.computedFlexBasis) { child.contents.layout.computedFlexBasis = fmaxf child.contents.style.flexBasis (getPaddingAndBorderAxis child.contents mainAxis) } } else { childWidth.contents = cssUndefined; childHeight.contents = cssUndefined; childWidthMeasureMode.contents = CssMeasureModeUndefined; childHeightMeasureMode.contents = CssMeasureModeUndefined; if (isStyleDimDefined child.contents CssFlexDirectionRow) { childWidth.contents = child.contents.style.width +. getMarginAxis child.contents CssFlexDirectionRow; childWidthMeasureMode.contents = CssMeasureModeExactly }; /** * Why can't this just be inlined to .height !== cssUndefined. */ if (isStyleDimDefined child.contents CssFlexDirectionColumn) { childHeight.contents = child.contents.style.height +. getMarginAxis child.contents CssFlexDirectionColumn; childHeightMeasureMode.contents = CssMeasureModeExactly }; if (not isMainAxisRow && node.style.overflow === Scroll || node.style.overflow !== Scroll) { if (isUndefined childWidth.contents && not (isUndefined width)) { childWidth.contents = width; childWidthMeasureMode.contents = CssMeasureModeAtMost } }; if (isMainAxisRow && node.style.overflow === Scroll || node.style.overflow !== Scroll) { if (isUndefined childHeight.contents && not (isUndefined height)) { childHeight.contents = height; childHeightMeasureMode.contents = CssMeasureModeAtMost } }; /* * If child has no defined size in the cross axis and is set to * stretch, set the cross axis to be measured exactly with the * available inner width. */ if ( not isMainAxisRow && not (isUndefined width) && not (isStyleDimDefined child.contents CssFlexDirectionRow) && widthMode === CssMeasureModeExactly && getAlignItem node child.contents === CssAlignStretch ) { childWidth.contents = width; childWidthMeasureMode.contents = CssMeasureModeExactly }; if ( isMainAxisRow && not (isUndefined height) && not (isStyleDimDefined child.contents CssFlexDirectionColumn) && heightMode === CssMeasureModeExactly && getAlignItem node child.contents === CssAlignStretch ) { childHeight.contents = height; childHeightMeasureMode.contents = CssMeasureModeExactly }; let _ = layoutNodeInternal child.contents childWidth.contents childHeight.contents direction childWidthMeasureMode.contents childHeightMeasureMode.contents false measureString; child.contents.layout.computedFlexBasis = fmaxf (isMainAxisRow ? child.contents.layout.measuredWidth : child.contents.layout.measuredHeight) (getPaddingAndBorderAxis child.contents mainAxis) } } /** * By default, mathematical operations are floating point. */ and layoutNodeImpl ( node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout ) => { /** START_GENERATED **/ /* re_assert */ /* (isUndefined availableWidth ? widthMeasureMode === CssMeasureModeUndefined : true) */ /* "availableWidth is indefinite so widthMeasureMode must be CssMeasureModeUndefined"; */ /* re_assert */ /* (isUndefined availableHeight ? heightMeasureMode === CssMeasureModeUndefined : true) */ /* "availableHeight is indefinite so heightMeasureMode must be CssMeasureModeUndefined"; */ let paddingAndBorderAxisRow = getPaddingAndBorderAxis node CssFlexDirectionRow; let paddingAndBorderAxisColumn = getPaddingAndBorderAxis node CssFlexDirectionColumn; let marginAxisRow = getMarginAxis node CssFlexDirectionRow; let marginAxisColumn = getMarginAxis node CssFlexDirectionColumn; let direction = resolveDirection node parentDirection; node.layout.direction = direction; /* For content (text) nodes, determine the dimensions based on the text contents. */ if (node.measure !== dummyMeasure && node.childrenCount === 0) { let innerWidth = availableWidth -. marginAxisRow -. paddingAndBorderAxisRow; let innerHeight = availableHeight -. marginAxisColumn -. paddingAndBorderAxisColumn; if (widthMeasureMode === CssMeasureModeExactly && heightMeasureMode === CssMeasureModeExactly) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (availableWidth -. marginAxisRow); node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn (availableHeight -. marginAxisColumn) } else if ( not (isUndefined innerWidth) && innerWidth <= zero || not (isUndefined innerHeight) && innerHeight <= zero ) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow zero; node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn zero } else { let measureDim = node.measure node innerWidth widthMeasureMode innerHeight heightMeasureMode; node.layout.measuredWidth = boundAxis node CssFlexDirectionRow ( widthMeasureMode === CssMeasureModeUndefined || widthMeasureMode === CssMeasureModeAtMost ? measureDim.width +. paddingAndBorderAxisRow : availableWidth -. marginAxisRow ); node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn ( heightMeasureMode === CssMeasureModeUndefined || heightMeasureMode === CssMeasureModeAtMost ? measureDim.height +. paddingAndBorderAxisColumn : availableHeight -. marginAxisColumn ) } } else { let childCount = Array.length node.children; if (childCount === 0) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow ( widthMeasureMode === CssMeasureModeUndefined || widthMeasureMode === CssMeasureModeAtMost ? paddingAndBorderAxisRow : availableWidth -. marginAxisRow ); node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn ( heightMeasureMode === CssMeasureModeUndefined || heightMeasureMode === CssMeasureModeAtMost ? paddingAndBorderAxisColumn : availableHeight -. marginAxisColumn ) } else { let shouldContinue = {contents: true}; if (not performLayout) { if ( ( ( widthMeasureMode === CssMeasureModeAtMost && not (isUndefined availableWidth) && availableWidth <= zero ) && heightMeasureMode === CssMeasureModeAtMost ) && not (isUndefined availableHeight) && availableHeight <= zero ) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow zero; node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn zero; shouldContinue.contents = false } else if ( widthMeasureMode === CssMeasureModeAtMost && not (isUndefined availableWidth) && availableWidth <= zero ) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow zero; node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn (isUndefined availableHeight ? zero : availableHeight -. marginAxisColumn); shouldContinue.contents = false } else if ( heightMeasureMode === CssMeasureModeAtMost && not (isUndefined availableHeight) && availableHeight <= zero ) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (isUndefined availableWidth ? zero : availableWidth -. marginAxisRow); node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn zero; shouldContinue.contents = false } else if ( widthMeasureMode === CssMeasureModeExactly && heightMeasureMode === CssMeasureModeExactly ) { node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (availableWidth -. marginAxisRow); node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn (availableHeight -. marginAxisColumn); shouldContinue.contents = false } }; if shouldContinue.contents { let mainAxis = resolveAxis node.style.flexDirection direction; let crossAxis = getCrossFlexDirection mainAxis direction; let isMainAxisRow = isRowDirection mainAxis; let justifyContent = node.style.justifyContent; let isNodeFlexWrap = node.style.flexWrap === CssWrap; let firstAbsoluteChild = {contents: theNullNode}; let currentAbsoluteChild = {contents: theNullNode}; let leadingPaddingAndBorderMain = getLeadingPaddingAndBorder node mainAxis; let trailingPaddingAndBorderMain = getTrailingPaddingAndBorder node mainAxis; let leadingPaddingAndBorderCross = getLeadingPaddingAndBorder node crossAxis; let paddingAndBorderAxisMain = getPaddingAndBorderAxis node mainAxis; let paddingAndBorderAxisCross = getPaddingAndBorderAxis node crossAxis; let measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; let measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; let availableInnerWidth = availableWidth -. marginAxisRow -. paddingAndBorderAxisRow; let availableInnerHeight = availableHeight -. marginAxisColumn -. paddingAndBorderAxisColumn; let availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; let availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; let child = {contents: theNullNode}; /* let i = 0; */ /* STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM */ for i in 0 to (childCount - 1) { child.contents = node.children.(i); if performLayout { let childDirection = resolveDirection child.contents direction; setPosition child.contents childDirection }; if (child.contents.style.positionType === CssPositionAbsolute) { if (firstAbsoluteChild.contents === theNullNode) { firstAbsoluteChild.contents = child.contents }; if (currentAbsoluteChild.contents !== theNullNode) { currentAbsoluteChild.contents.nextChild = child.contents }; currentAbsoluteChild.contents = child.contents; child.contents.nextChild = theNullNode } else { computeChildFlexBasis node child availableInnerWidth widthMeasureMode availableInnerHeight heightMeasureMode direction } }; /* STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES */ let startOfLineIndex = {contents: 0}; let endOfLineIndex = {contents: 0}; let lineCount = {contents: 0}; let totalLineCrossDim = {contents: zero}; let maxLineMainDim = {contents: zero}; while (endOfLineIndex.contents < childCount) { let itemsOnLine = {contents: 0}; let sizeConsumedOnCurrentLine = {contents: zero}; let totalFlexGrowFactors = {contents: zero}; let totalFlexShrinkScaledFactors = {contents: zero}; let curIndex = {contents: startOfLineIndex.contents}; let firstRelativeChild = {contents: theNullNode}; let currentRelativeChild = {contents: theNullNode}; let shouldContinue = {contents: true}; while (curIndex.contents < childCount && shouldContinue.contents) { child.contents = node.children.(curIndex.contents); child.contents.lineIndex = lineCount.contents; if (child.contents.style.positionType !== CssPositionAbsolute) { let outerFlexBasis = child.contents.layout.computedFlexBasis +. getMarginAxis child.contents mainAxis; if ( ( sizeConsumedOnCurrentLine.contents +. outerFlexBasis > availableInnerMainDim && isNodeFlexWrap ) && itemsOnLine.contents > 0 ) { shouldContinue.contents = false } else { sizeConsumedOnCurrentLine.contents = sizeConsumedOnCurrentLine.contents +. outerFlexBasis; itemsOnLine.contents = itemsOnLine.contents + 1; if (isFlex child.contents) { totalFlexGrowFactors.contents = totalFlexGrowFactors.contents +. child.contents.style.flexGrow; totalFlexShrinkScaledFactors.contents = totalFlexShrinkScaledFactors.contents +. -. child.contents.style.flexShrink *. child.contents.layout.computedFlexBasis }; if (firstRelativeChild.contents === theNullNode) { firstRelativeChild.contents = child.contents }; if (currentRelativeChild.contents !== theNullNode) { currentRelativeChild.contents.nextChild = child.contents }; currentRelativeChild.contents = child.contents; child.contents.nextChild = theNullNode; curIndex.contents = curIndex.contents + 1; endOfLineIndex.contents = endOfLineIndex.contents + 1 } } else { curIndex.contents = curIndex.contents + 1; endOfLineIndex.contents = endOfLineIndex.contents + 1 } }; let canSkipFlex = not performLayout && measureModeCrossDim === CssMeasureModeExactly; let leadingMainDim = {contents: zero}; let betweenMainDim = {contents: zero}; let remainingFreeSpace = {contents: zero}; if (not (isUndefined availableInnerMainDim)) { remainingFreeSpace.contents = availableInnerMainDim -. sizeConsumedOnCurrentLine.contents } else if ( sizeConsumedOnCurrentLine.contents < zero ) { remainingFreeSpace.contents = -. sizeConsumedOnCurrentLine.contents }; let originalRemainingFreeSpace = remainingFreeSpace.contents; let deltaFreeSpace = {contents: zero}; if (not canSkipFlex) { let childFlexBasis = {contents: zero}; let flexShrinkScaledFactor = {contents: zero}; let flexGrowFactor = {contents: zero}; let baseMainSize = {contents: zero}; let boundMainSize = {contents: zero}; let deltaFlexShrinkScaledFactors = {contents: zero}; let deltaFlexGrowFactors = {contents: zero}; currentRelativeChild.contents = firstRelativeChild.contents; while (currentRelativeChild.contents !== theNullNode) { childFlexBasis.contents = currentRelativeChild.contents.layout.computedFlexBasis; if (remainingFreeSpace.contents < zero) { flexShrinkScaledFactor.contents = -. currentRelativeChild.contents.style.flexShrink *. childFlexBasis.contents; if (flexShrinkScaledFactor.contents != zero) { baseMainSize.contents = childFlexBasis.contents +. /* * Important to first scale, then divide - to support fixed * point encoding. */ flexShrinkScaledFactor.contents *. remainingFreeSpace.contents /. totalFlexShrinkScaledFactors.contents; boundMainSize.contents = boundAxis currentRelativeChild.contents mainAxis baseMainSize.contents; if (baseMainSize.contents != boundMainSize.contents) { deltaFreeSpace.contents = deltaFreeSpace.contents -. (boundMainSize.contents -. childFlexBasis.contents); deltaFlexShrinkScaledFactors.contents = deltaFlexShrinkScaledFactors.contents -. flexShrinkScaledFactor.contents } } } else if ( remainingFreeSpace.contents > zero ) { flexGrowFactor.contents = currentRelativeChild.contents.style.flexGrow; if (flexGrowFactor.contents != zero) { baseMainSize.contents = childFlexBasis.contents +. /* * Important to first scale, then divide - to support fixed * point encoding. */ flexGrowFactor.contents *. remainingFreeSpace.contents /. totalFlexGrowFactors.contents; boundMainSize.contents = boundAxis currentRelativeChild.contents mainAxis baseMainSize.contents; if (baseMainSize.contents != boundMainSize.contents) { deltaFreeSpace.contents = deltaFreeSpace.contents -. (boundMainSize.contents -. childFlexBasis.contents); deltaFlexGrowFactors.contents = deltaFlexGrowFactors.contents -. flexGrowFactor.contents } } }; currentRelativeChild.contents = currentRelativeChild.contents.nextChild }; totalFlexShrinkScaledFactors.contents = totalFlexShrinkScaledFactors.contents +. deltaFlexShrinkScaledFactors.contents; totalFlexGrowFactors.contents = totalFlexGrowFactors.contents +. deltaFlexGrowFactors.contents; remainingFreeSpace.contents = remainingFreeSpace.contents +. deltaFreeSpace.contents; deltaFreeSpace.contents = zero; currentRelativeChild.contents = firstRelativeChild.contents; while (currentRelativeChild.contents !== theNullNode) { childFlexBasis.contents = currentRelativeChild.contents.layout.computedFlexBasis; let updatedMainSize = {contents: childFlexBasis.contents}; if (remainingFreeSpace.contents < zero) { flexShrinkScaledFactor.contents = -. currentRelativeChild.contents.style.flexShrink *. childFlexBasis.contents; if (flexShrinkScaledFactor.contents != zero) { updatedMainSize.contents = boundAxis currentRelativeChild.contents mainAxis ( childFlexBasis.contents +. /* * Important to first scale, then divide - to support * fixed point encoding. */ flexShrinkScaledFactor.contents *. remainingFreeSpace.contents /. totalFlexShrinkScaledFactors.contents ) } } else if ( remainingFreeSpace.contents > zero ) { flexGrowFactor.contents = currentRelativeChild.contents.style.flexGrow; if (flexGrowFactor.contents != zero) { updatedMainSize.contents = boundAxis currentRelativeChild.contents mainAxis ( childFlexBasis.contents +. /* * Important to first scale, then divide - to support * fixed point encoding. */ flexGrowFactor.contents *. remainingFreeSpace.contents /. totalFlexGrowFactors.contents ) } }; deltaFreeSpace.contents = deltaFreeSpace.contents -. (updatedMainSize.contents -. childFlexBasis.contents); let childWidth = {contents: zero}; let childHeight = {contents: zero}; let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; if isMainAxisRow { childWidth.contents = updatedMainSize.contents +. getMarginAxis currentRelativeChild.contents CssFlexDirectionRow; childWidthMeasureMode.contents = CssMeasureModeExactly; if ( not (isUndefined availableInnerCrossDim) && not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionColumn) && heightMeasureMode === CssMeasureModeExactly && getAlignItem node currentRelativeChild.contents === CssAlignStretch ) { childHeight.contents = availableInnerCrossDim; childHeightMeasureMode.contents = CssMeasureModeExactly } else if ( not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionColumn) ) { childHeight.contents = availableInnerCrossDim; childHeightMeasureMode.contents = isUndefined childHeight.contents ? CssMeasureModeUndefined : CssMeasureModeAtMost } else { childHeight.contents = currentRelativeChild.contents.style.height +. getMarginAxis currentRelativeChild.contents CssFlexDirectionColumn; childHeightMeasureMode.contents = CssMeasureModeExactly } } else { childHeight.contents = updatedMainSize.contents +. getMarginAxis currentRelativeChild.contents CssFlexDirectionColumn; childHeightMeasureMode.contents = CssMeasureModeExactly; if ( not (isUndefined availableInnerCrossDim) && not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionRow) && widthMeasureMode === CssMeasureModeExactly && getAlignItem node currentRelativeChild.contents === CssAlignStretch ) { childWidth.contents = availableInnerCrossDim; childWidthMeasureMode.contents = CssMeasureModeExactly } else if ( not (isStyleDimDefined currentRelativeChild.contents CssFlexDirectionRow) ) { childWidth.contents = availableInnerCrossDim; childWidthMeasureMode.contents = isUndefined childWidth.contents ? CssMeasureModeUndefined : CssMeasureModeAtMost } else { childWidth.contents = currentRelativeChild.contents.style.width +. getMarginAxis currentRelativeChild.contents CssFlexDirectionRow; childWidthMeasureMode.contents = CssMeasureModeExactly } }; let requiresStretchLayout = not (isStyleDimDefined currentRelativeChild.contents crossAxis) && getAlignItem node currentRelativeChild.contents === CssAlignStretch; let _ = layoutNodeInternal currentRelativeChild.contents childWidth.contents childHeight.contents direction childWidthMeasureMode.contents childHeightMeasureMode.contents (performLayout && not requiresStretchLayout) flexString; currentRelativeChild.contents = currentRelativeChild.contents.nextChild } }; remainingFreeSpace.contents = originalRemainingFreeSpace +. deltaFreeSpace.contents; /* If we are using "at most" rules in the main axis. Calculate the remaining space when constraint by the min size defined for the main axis. */ if (measureModeMainDim === CssMeasureModeAtMost) { let minDim = styleMinDimensionForAxis node mainAxis; if (not (isUndefined minDim) && minDim >= 0) { remainingFreeSpace.contents = fmaxf 0 (minDim - (availableInnerMainDim -. remainingFreeSpace.contents)) } else { remainingFreeSpace.contents = zero } }; switch justifyContent { | CssJustifyCenter => leadingMainDim.contents = divideScalarByInt remainingFreeSpace.contents 2 | CssJustifyFlexEnd => leadingMainDim.contents = remainingFreeSpace.contents | CssJustifySpaceBetween => if (itemsOnLine.contents > 1) { betweenMainDim.contents = divideScalarByInt (fmaxf remainingFreeSpace.contents zero) (itemsOnLine.contents - 1) } else { betweenMainDim.contents = zero } | CssJustifySpaceAround => betweenMainDim.contents = divideScalarByInt remainingFreeSpace.contents itemsOnLine.contents; leadingMainDim.contents = divideScalarByInt betweenMainDim.contents 2 | CssJustifyFlexStart => () }; let mainDim = {contents: leadingPaddingAndBorderMain +. leadingMainDim.contents}; let crossDim = {contents: zero}; for i in startOfLineIndex.contents to (endOfLineIndex.contents - 1) { child.contents = node.children.(i); if ( child.contents.style.positionType === CssPositionAbsolute && isLeadingPosDefinedWithFallback child.contents mainAxis ) { if performLayout { setLayoutLeadingPositionForAxis child.contents mainAxis ( getLeadingPositionWithFallback child.contents mainAxis +. getLeadingBorder node mainAxis +. getLeadingMargin child.contents mainAxis ) } } else { if performLayout { setLayoutLeadingPositionForAxis child.contents mainAxis (layoutPosPositionForAxis child.contents mainAxis +. mainDim.contents) }; if (child.contents.style.positionType === CssPositionRelative) { if canSkipFlex { mainDim.contents = mainDim.contents +. betweenMainDim.contents +. getMarginAxis child.contents mainAxis +. child.contents.layout.computedFlexBasis; crossDim.contents = availableInnerCrossDim } else { mainDim.contents = mainDim.contents +. betweenMainDim.contents +. getDimWithMargin child.contents mainAxis; crossDim.contents = fmaxf crossDim.contents (getDimWithMargin child.contents crossAxis) } } } }; mainDim.contents = mainDim.contents +. trailingPaddingAndBorderMain; let containerCrossAxis = {contents: availableInnerCrossDim}; if ( measureModeCrossDim === CssMeasureModeUndefined || measureModeCrossDim === CssMeasureModeAtMost ) { containerCrossAxis.contents = boundAxis node crossAxis (crossDim.contents +. paddingAndBorderAxisCross) -. paddingAndBorderAxisCross; if (measureModeCrossDim === CssMeasureModeAtMost) { containerCrossAxis.contents = fminf containerCrossAxis.contents availableInnerCrossDim } }; if (not isNodeFlexWrap && measureModeCrossDim === CssMeasureModeExactly) { crossDim.contents = availableInnerCrossDim }; crossDim.contents = boundAxis node crossAxis (crossDim.contents +. paddingAndBorderAxisCross) -. paddingAndBorderAxisCross; /* * STEP 7: CROSS-AXIS ALIGNMENT We can skip child alignment if we're * just measuring the container. */ if performLayout { for i in startOfLineIndex.contents to (endOfLineIndex.contents - 1) { child.contents = node.children.(i); if (child.contents.style.positionType === CssPositionAbsolute) { if (isLeadingPosDefinedWithFallback child.contents crossAxis) { setLayoutLeadingPositionForAxis child.contents crossAxis ( getLeadingPositionWithFallback child.contents crossAxis +. getLeadingBorder node crossAxis +. getLeadingMargin child.contents crossAxis ) } else { setLayoutLeadingPositionForAxis child.contents crossAxis (leadingPaddingAndBorderCross +. getLeadingMargin child.contents crossAxis) } } else { let leadingCrossDim = {contents: leadingPaddingAndBorderCross}; let alignItem = getAlignItem node child.contents; if (alignItem === CssAlignStretch) { let childWidth = {contents: zero}; let childHeight = {contents: zero}; let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; childWidth.contents = child.contents.layout.measuredWidth +. getMarginAxis child.contents CssFlexDirectionRow; childHeight.contents = child.contents.layout.measuredHeight +. getMarginAxis child.contents CssFlexDirectionColumn; let isCrossSizeDefinite = {contents: false}; if isMainAxisRow { isCrossSizeDefinite.contents = isStyleDimDefined child.contents CssFlexDirectionColumn; childHeight.contents = crossDim.contents } else { isCrossSizeDefinite.contents = isStyleDimDefined child.contents CssFlexDirectionRow; childWidth.contents = crossDim.contents }; if (not isCrossSizeDefinite.contents) { childWidthMeasureMode.contents = isUndefined childWidth.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; childHeightMeasureMode.contents = isUndefined childHeight.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; let _ = layoutNodeInternal child.contents childWidth.contents childHeight.contents direction childWidthMeasureMode.contents childHeightMeasureMode.contents true stretchString; () } } else if ( alignItem !== CssAlignFlexStart ) { let remainingCrossDim = containerCrossAxis.contents -. getDimWithMargin child.contents crossAxis; if (alignItem === CssAlignCenter) { leadingCrossDim.contents = leadingCrossDim.contents +. divideScalarByInt remainingCrossDim 2 } else { leadingCrossDim.contents = leadingCrossDim.contents +. remainingCrossDim } }; setLayoutLeadingPositionForAxis child.contents crossAxis ( layoutPosPositionForAxis child.contents crossAxis +. totalLineCrossDim.contents +. leadingCrossDim.contents ) } } }; totalLineCrossDim.contents = totalLineCrossDim.contents +. crossDim.contents; maxLineMainDim.contents = fmaxf maxLineMainDim.contents mainDim.contents; lineCount.contents = lineCount.contents + 1; startOfLineIndex.contents = endOfLineIndex.contents }; if (lineCount.contents > 1 && performLayout && not (isUndefined availableInnerCrossDim)) { let remainingAlignContentDim = availableInnerCrossDim -. totalLineCrossDim.contents; let crossDimLead = {contents: zero}; let currentLead = {contents: leadingPaddingAndBorderCross}; let alignContent = node.style.alignContent; if (alignContent === CssAlignFlexEnd) { currentLead.contents = currentLead.contents +. remainingAlignContentDim } else if ( alignContent === CssAlignCenter ) { currentLead.contents = currentLead.contents +. divideScalarByInt remainingAlignContentDim 2 } else if ( alignContent === CssAlignStretch ) { if (availableInnerCrossDim > totalLineCrossDim.contents) { crossDimLead.contents = divideScalarByInt remainingAlignContentDim lineCount.contents } }; let endIndex = {contents: 0}; for i in 0 to (lineCount.contents - 1) { let startIndex = endIndex.contents; let j = {contents: startIndex}; let lineHeight = {contents: zero}; let shouldContinue = {contents: false}; while (j.contents < childCount && shouldContinue.contents) { child.contents = node.children.(j.contents); if (child.contents.style.positionType === CssPositionRelative) { if (child.contents.lineIndex !== i) { shouldContinue.contents = false } else if ( isLayoutDimDefined child.contents crossAxis ) { lineHeight.contents = fmaxf lineHeight.contents ( layoutMeasuredDimensionForAxis child.contents crossAxis +. getMarginAxis child.contents crossAxis ) } }; j.contents = j.contents + 1 }; endIndex.contents = j.contents; lineHeight.contents = lineHeight.contents +. crossDimLead.contents; if performLayout { for j in startIndex to (endIndex.contents - 1) { child.contents = node.children.(j); if (child.contents.style.positionType === CssPositionRelative) { switch (getAlignItem node child.contents) { | CssAlignFlexStart => setLayoutLeadingPositionForAxis child.contents crossAxis (currentLead.contents +. getLeadingMargin child.contents crossAxis) | CssAlignFlexEnd => setLayoutLeadingPositionForAxis child.contents crossAxis ( currentLead.contents +. lineHeight.contents -. getTrailingMargin child.contents crossAxis -. layoutMeasuredDimensionForAxis child.contents crossAxis ) | CssAlignCenter => let childHeight = layoutMeasuredDimensionForAxis child.contents crossAxis; setLayoutLeadingPositionForAxis child.contents crossAxis (currentLead.contents +. divideScalarByInt (lineHeight.contents -. childHeight) 2) | CssAlignStretch => setLayoutLeadingPositionForAxis child.contents crossAxis (currentLead.contents +. getLeadingMargin child.contents crossAxis) | CssAlignAuto => raise (Invalid_argument "getAlignItem should never return auto") } } } }; currentLead.contents = currentLead.contents +. lineHeight.contents } }; /* STEP 9: COMPUTING FINAL DIMENSIONS */ node.layout.measuredWidth = boundAxis node CssFlexDirectionRow (availableWidth -. marginAxisRow); node.layout.measuredHeight = boundAxis node CssFlexDirectionColumn (availableHeight -. marginAxisColumn); /* If the user didn't specify a width or height for the node, set the * dimensions based on the children. */ if (measureModeMainDim === CssMeasureModeUndefined) { setLayoutMeasuredDimensionForAxis node mainAxis (boundAxis node mainAxis maxLineMainDim.contents) } else if ( measureModeMainDim === CssMeasureModeAtMost ) { setLayoutMeasuredDimensionForAxis node mainAxis ( fmaxf ( fminf (availableInnerMainDim +. paddingAndBorderAxisMain) (boundAxisWithinMinAndMax node mainAxis maxLineMainDim.contents) ) paddingAndBorderAxisMain ) }; if (measureModeCrossDim === CssMeasureModeUndefined) { setLayoutMeasuredDimensionForAxis node crossAxis (boundAxis node crossAxis (totalLineCrossDim.contents +. paddingAndBorderAxisCross)) } else if ( measureModeCrossDim === CssMeasureModeAtMost ) { setLayoutMeasuredDimensionForAxis node crossAxis ( fmaxf ( fminf (availableInnerCrossDim +. paddingAndBorderAxisCross) ( boundAxisWithinMinAndMax node crossAxis (totalLineCrossDim.contents +. paddingAndBorderAxisCross) ) ) paddingAndBorderAxisCross ) }; currentAbsoluteChild.contents = firstAbsoluteChild.contents; while (currentAbsoluteChild.contents !== theNullNode) { if performLayout { let childWidth = {contents: cssUndefined}; let childHeight = {contents: cssUndefined}; let childWidthMeasureMode = {contents: CssMeasureModeUndefined}; let childHeightMeasureMode = {contents: CssMeasureModeUndefined}; if (isStyleDimDefined currentAbsoluteChild.contents CssFlexDirectionRow) { childWidth.contents = currentAbsoluteChild.contents.style.width +. getMarginAxis currentAbsoluteChild.contents CssFlexDirectionRow } else if ( isLeadingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionRow && isTrailingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionRow ) { childWidth.contents = node.layout.measuredWidth -. ( getLeadingBorder node CssFlexDirectionRow +. getTrailingBorder node CssFlexDirectionRow ) -. ( getLeadingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionRow +. getTrailingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionRow ); childWidth.contents = boundAxis currentAbsoluteChild.contents CssFlexDirectionRow childWidth.contents }; if (isStyleDimDefined currentAbsoluteChild.contents CssFlexDirectionColumn) { childHeight.contents = currentAbsoluteChild.contents.style.height +. getMarginAxis currentAbsoluteChild.contents CssFlexDirectionColumn } else if ( /* If the child doesn't have a specified height, compute the height based on the top/bottom offsets if they're defined. */ isLeadingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn && isTrailingPosDefinedWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn ) { childHeight.contents = node.layout.measuredHeight -. ( getLeadingBorder node CssFlexDirectionColumn +. getTrailingBorder node CssFlexDirectionColumn ) -. ( getLeadingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn +. getTrailingPositionWithFallback currentAbsoluteChild.contents CssFlexDirectionColumn ); childHeight.contents = boundAxis currentAbsoluteChild.contents CssFlexDirectionColumn childHeight.contents }; if (isUndefined childWidth.contents || isUndefined childHeight.contents) { childWidthMeasureMode.contents = isUndefined childWidth.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; childHeightMeasureMode.contents = isUndefined childHeight.contents ? CssMeasureModeUndefined : CssMeasureModeExactly; /* * According to the spec, if the main size is not definite and the * child's inline axis is parallel to the main axis (i.e. it's * horizontal), the child should be sized using "UNDEFINED" in * the main size. Otherwise use "AT_MOST" in the cross axis. */ if ( (not isMainAxisRow && isUndefined childWidth.contents) && not (isUndefined availableInnerWidth) ) { childWidth.contents = availableInnerWidth; childWidthMeasureMode.contents = CssMeasureModeAtMost }; /* * If child has no defined size in the cross axis and is set to stretch, set the cross * axis to be measured exactly with the available inner width */ let _ = layoutNodeInternal currentAbsoluteChild.contents childWidth.contents childHeight.contents direction childWidthMeasureMode.contents childHeightMeasureMode.contents false absMeasureString; childWidth.contents = currentAbsoluteChild.contents.layout.measuredWidth +. getMarginAxis currentAbsoluteChild.contents CssFlexDirectionRow; childHeight.contents = currentAbsoluteChild.contents.layout.measuredHeight +. getMarginAxis currentAbsoluteChild.contents CssFlexDirectionColumn }; let _ = layoutNodeInternal currentAbsoluteChild.contents childWidth.contents childHeight.contents direction CssMeasureModeExactly CssMeasureModeExactly true absLayoutString; if ( isTrailingPosDefinedWithFallback currentAbsoluteChild.contents mainAxis && not (isLeadingPosDefinedWithFallback currentAbsoluteChild.contents mainAxis) ) { setLayoutLeadingPositionForAxis currentAbsoluteChild.contents mainAxis ( layoutMeasuredDimensionForAxis node mainAxis -. layoutMeasuredDimensionForAxis currentAbsoluteChild.contents mainAxis -. getTrailingPositionWithFallback currentAbsoluteChild.contents mainAxis ) }; if ( isTrailingPosDefinedWithFallback currentAbsoluteChild.contents crossAxis && not (isLeadingPosDefinedWithFallback currentAbsoluteChild.contents crossAxis) ) { setLayoutLeadingPositionForAxis currentAbsoluteChild.contents crossAxis ( layoutMeasuredDimensionForAxis node crossAxis -. layoutMeasuredDimensionForAxis currentAbsoluteChild.contents crossAxis -. getTrailingPositionWithFallback currentAbsoluteChild.contents crossAxis ) } }; currentAbsoluteChild.contents = currentAbsoluteChild.contents.nextChild }; /* STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN */ if performLayout { let needsMainTrailingPos = mainAxis == CssFlexDirectionRowReverse || mainAxis == CssFlexDirectionColumnReverse; let needsCrossTrailingPos = crossAxis == CssFlexDirectionRowReverse || crossAxis == CssFlexDirectionColumnReverse; /* Set trailing position if necessary. */ if (needsMainTrailingPos || needsCrossTrailingPos) { for i in 0 to (childCount - 1) { let child = node.children.(i); if needsMainTrailingPos { setTrailingPosition node child mainAxis }; if needsCrossTrailingPos { setTrailingPosition node child crossAxis } } } } } } } /** END_GENERATED **/ }; let layoutNode node availableWidth availableHeight parentDirection => { /* Increment the generation count. This will force the recursive routine to visit*/ /* all dirty nodes at least once. Subsequent visits will be skipped if the input*/ /* parameters don't change.*/ gCurrentGenerationCount.contents = gCurrentGenerationCount.contents + 1; /* If the caller didn't specify a height/width, use the dimensions*/ /* specified in the style.*/ let (availableWidth, widthMeasureMode) = if (not (isUndefined availableWidth)) { (availableWidth, CssMeasureModeExactly) } else if ( isStyleDimDefined node CssFlexDirectionRow ) { (node.style.width +. getMarginAxis node CssFlexDirectionRow, CssMeasureModeExactly) } else if ( node.style.maxWidth >= zero ) { (node.style.maxWidth, CssMeasureModeAtMost) } else { (availableWidth, CssMeasureModeUndefined) }; let (availableHeight, heightMeasureMode) = if (not (isUndefined availableHeight)) { (availableHeight, CssMeasureModeExactly) } else if ( isStyleDimDefined node CssFlexDirectionColumn ) { (node.style.height +. getMarginAxis node CssFlexDirectionColumn, CssMeasureModeExactly) } else if ( node.style.maxHeight >= zero ) { (node.style.maxHeight, CssMeasureModeAtMost) } else { (availableHeight, CssMeasureModeUndefined) }; if ( layoutNodeInternal node availableWidth availableHeight parentDirection widthMeasureMode heightMeasureMode true initialString ) { setPosition node node.layout.direction; if gPrintTree.contents { LayoutPrint.printCssNode (node, {printLayout: true, printChildren: true, printStyle: true}) } } };