Class FlexboxLayoutAlgorithm440
Summer 2023: Adds detailed complex fixes for edge-cases around scrollviews and percentages-of-percentages-or-auto-sizes Fixes issues with WRAPPED layouts mis-reporting their sizes in ways that confuses the calculations of Unity Scrollviews
Implements the CSS3 FlexBox Specification following the suggested algorithm in the specification itself (which is not 100% optimal in performance, but produces the 'most correct' visual results).
Inheritance
Implements
Inherited Members
Namespace: NinjaTools.FlexBuilder.LayoutAlgorithms
Assembly: cs.temp.dll.dll
Syntax
public class FlexboxLayoutAlgorithm440 : ILayoutAlgorithmV44xPlus, IFlexboxDescribableLayoutAlgorithm
Properties
Methods
- (ContentLength) _DefiniteInnerContentLengthOfContainer(Axis, FlexContainer, Boolean)
Implements recursive "find or calculate the DEFINITE length of a Container in specified Axis", according to the rules from main CSS3 Specification; does NOT add the extra definitions of Flexbox 9.8 which require additional context for you to implement them.
September 2023: this is the MAIN part of the v4.3.0 rewrite: it more perfectly calculates the 'available space' within a FlexContainer, with fewer kludges, and the hacks and special-casing removed/resolved into their purest form.
- Ultimately we want "recursively-up evaluated, content-box-size-of-parent-if-definite-otherwise-infinite ... WITHOUT inspecting any children"
Parameters
Axis | axis | |
FlexContainer | container | |
Boolean | debug |
Returns
ContentLength |
Exceptions
Exception |
- (Boolean) _HasDefiniteInnerContentLength(FlexContainer, Axis, out ContentLength)
Improved version of _DefiniteInnerContentLengthOfContainer(Axis, FlexContainer, Boolean) that lets you check for validity at the same time (NECESSARY because of bugs in Microsoft's C# compiler where it cannot do static analysis correctly, tragically)
Parameters
FlexContainer | container | |
Axis | axis | |
ContentLength | definiteInnerContentLength |
Returns
Boolean |
- (Vector2) ContainerContentSize(FlexContainer, CSSConstrainedDimensions, CSSAvailableSpace2, Boolean)
Embedded inside
Within the spec, this is performing the role of:
'size a FlexContainer (under 0 or more constraints) by performing layout of its internal (children) contents'.
NB: does not need to know the definite "other" axis size even if that's definite, because internally it's going to recalculate the definiteness of BOTH axes and might as well do both as do one
Parameters
FlexContainer | itemIsContainer | |
CSSConstrainedDimensions | minOrMaxEachAxis | |
CSSAvailableSpace2 | availableSpaceInsideContainersContainer | |
Boolean | containerIsShowDebugMessages |
Returns
Vector2 |
Exceptions
Exception |
- (ContentLength) ContentLengthOfContainerOrItem(FlexItem, Axis, CSSConstrainedDimensions, BoxLength, CSSAvailableSpace2, Boolean)
Getting the MAIN-axis content-length for an item happens first, and has less contextual info than when getting the
CROSS-axis; this method does the MAIN,
NOTE: This is (historically) where ALMOST ALL execution time happens: either being forced to recursively relayout child FlexContainer(s) and/or being forced to query the Unity Transform tree and run heuristics to decide what an appropriate ContentSize is for a given GameObject.
Parameters
FlexItem | item | |
Axis | axis | |
CSSConstrainedDimensions | contentMinOrMax | |
BoxLength | overrideCrossAxisLength | |
CSSAvailableSpace2 | availableSpaceInContainer | |
Boolean | containerIsShowDebugMessages |
Returns
ContentLength |
- (Single) cssClampLengthToInnerSizes(FlexItem, ContentLength, RectTransform.Axis, CSSAvailableLength)
Special: the supplied length is a ContentLength, this method clamps against the INNER bounds of the item's MIN and MAX (instead of clamping against them as pure floats)
Parameters
FlexItem | item | |
ContentLength | innerLength | |
RectTransform.Axis | axis | |
CSSAvailableLength | containerInnerLength |
Returns
Single |
- (Boolean) HasDefiniteBoxLength(FlexContainer, Axis, out BoxLength, Boolean)
The 'main' calc for 'has definite size (in an axis)'. Notably this looks for, and returns, BoxLengths (which is 90% of CSS) - but there's a narrower method HasDefiniteWidthOrHeight(FlexItem, Axis, CSSAvailableLength, DefiniteContainingBlockSize, Nullable<Int32>, out BoxLength) for calculating width|height (which should ONLY be called when you know flexbasis doesn't apply or is AUTO).
In general, use this method unless you know for certain that you need one of the others; the three methods use each other where appropriate.
Internally uses HasDefiniteWidthOrHeight(FlexItem, Axis, CSSAvailableLength, DefiniteContainingBlockSize, Nullable<Int32>, out BoxLength) for base-cases, but adds SOME of the extra cases that are defined by Spec 9.8 - note that it does not care whether you pass-in a FlexItem or a FlexContainer, it does its own checks for BOTH.
NOTE: this contains code that is also used/reimplemented by NinjaTools.FlexBuilder.LayoutAlgorithms.FlexboxLayoutAlgorithm440.Spec_9_2_3_A_HasDefiniteUsedFlexBasis(NinjaTools.FlexBuilder.FlexItem,Axis,NinjaTools.FlexBuilder.LayoutAlgorithms.CSSAvailableLength,DefiniteContainingBlockSize,BoxLength@,FlexBasis@,System.Boolean).
Spec 9.8: https://www.w3.org/TR/css-flexbox-1/#definite-sizes
"Although CSS Sizing [CSS-SIZING-3] defines definite and indefinite lengths, Flexbox has several additional cases where a length can be considered definite ..."
Parameters
FlexContainer | container | |
Axis | axis | |
BoxLength | definiteLength | |
Boolean | isParentASingleLineFlexContainer | Used BOTH for Spec-9.8.1 calculations AND ALSO for pseudo-definite resolution against the element's containingblock (if provided) |
Returns
Boolean |
- (Boolean)
HasDefiniteContentLengthFromBoxLength(FlexItem, Axis, Single, Func, out ContentLength)
Parameters
FlexItem | item | |
Axis | axis | |
Single | definiteBoxLength | |
Func<ContentLength> | lazilyGenerateParentsInnerContentLength | |
ContentLength | definiteContentLEegth |
Returns
Boolean |
- (Boolean) HasDefiniteOuterCrossLengthInSingleLineParent(FlexItem, Axis, out Single)
As per HasDefiniteBoxLength(FlexContainer, Axis, out BoxLength, Boolean) except specialized for Spec 9.8.1 whch is ONLY valid when calculating "outer" cross-lengths, instead of the more normal 'box' lengths (main or cross).
Parameters
FlexItem | item | |
Axis | axis | |
Single | definiteOuterLength |
Returns
Boolean |
- (Boolean) HasDefiniteParentLength(FlexItem, Axis, CSSAvailableLength, out ContentLength)
Calculates (depending on internal setting) the 'true' definite-nes of the parent's length in the specified axis; OR (depending on internal setting) shortcuts it using a resolution against the 'available space inside parent' which COULD BE the same (TBC).
Parameters
FlexItem | element | |
Axis | axis | |
CSSAvailableLength | availableLengthInParent | |
ContentLength | definiteParentInnerLength |
Returns
Boolean |
Exceptions
Exception |
- (Boolean)
HasDefiniteWidthOrHeight(FlexItem, Axis, CSSAvailableLength, DefiniteContainingBlockSize, Nullable, out BoxLength)
Parameters
FlexItem | element | |
Axis | axis | |
CSSAvailableLength | availableLengthInParent | |
DefiniteContainingBlockSize | parentsSizeAsAContainingBlock | |
Nullable<Int32> | numFlexLines | |
BoxLength | definiteChildLength |
Returns
Boolean |
- (Boolean) HasValidSize(FlexContainer)
Current implementation: any size with zero OR positive lengths in both dimensions is 'valid' (even if it is 0-width or 0-height); the only 'invalid' sizes are negative ones.
TODO: upgrade this system to actually store explicit valid/invalid data either on each FlexContainer, or in a parallel data structure.
Parameters
FlexContainer | container |
Returns
Boolean |
- (ContentLength)
InnerContentLengthOfContainerIfAlreadyKnown(FlexItem, Nullable, Axis)
The problem: There is a bug in the official spec, in 9.2.2 it glosses-over a core problem: the authors never calculate the value of "the space available to the flex container" (they only ever calculate the space available WITHIN the container, ie available to the container's children) -- and they naively characterise this as "might result in an infinite value" -- but the reality is: it should rarely result in infinite, and it's up to us as implementers to do something considerably more intelligent and responsible than the spec authors did.
This method provides an extensible/replaceable way of making the decision.
Ideal is: scan upwards through the FlexContainer hierarchy until we EITHER find an ancestor FlexContainer that is 'invalid' in size (forcing us to return 'null', aka infinite), OR we find an ancestor with definite INNER size.
Default is: check only the immediate 'container's parent container', and use the rules for %age in an unknown context, which in CSS are: 1. %age width/max-width/height/min-height etc convert to 'AUTO', and 2. padding converts to '0'.
If there's no parent container, or the parent has invalid size, we return null, forcing the Spec-9.3 code to NEVER WRAP lines (because it interprets that as 'infinite space'.
NOTE: The PRIMARY challenge is when you encounter percentage values of padding: if the initial container has non-percentage padding then this method terminates immediately, as it requires only a simple check on "is container's parent container valid or not?"
Parameters
FlexItem | containerAsItem | |
Nullable<Single> | boxLength | |
Axis | axis |
Returns
ContentLength |
- (ContentLength) IntrinsicContentLengthOfPlainUIElement(FlexItem, Axis, CSSConstrainedDimensions, BoxLength, CSSAvailableSpace2, Boolean)
WARNING: currently to maintain compatibility with old content-sizers it cheats internally
Parameters
FlexItem | item | |
Axis | axis | |
CSSConstrainedDimensions | constraints | |
BoxLength | overrideOtherAxisLength | |
CSSAvailableSpace2 | availableSpaceInContainer | |
Boolean | containerIsShowDebugMessages |
Returns
ContentLength |
- (BoxLength) MinMainSizeForItemInAxis(FlexItem, Axis, CSSAvailableLength, Boolean)
Used by 9.7.x to limit the shrinking of items during flex-shrink
Specification: (incomprehensibly complex) "Clamp each non-frozen item’s target main size by its used min and max main sizes and floor its content-box size at zero." Web-browsers implementation: (slightly more sane, actually how it's done) "Clamp by its used min if it has one, or else by its used size if it has one, or else by its intrinsic minimum content-size in that dimension"
Parameters
FlexItem | element | |
Axis | axis | |
CSSAvailableLength | parentSizeForPercentagesInAxis | |
Boolean | showDebugMessages |
Returns
BoxLength |
- ((T1, T2)<List<FlexLine>, Dictionary<FlexItem, Vector2>>) Phase1CalculateChildSizes(FlexContainer, CSSAvailableSpace2, DefiniteContainingBlockSize)
Main method for calculating FlexItem SIZES
(called by the layout algorithm internally, before it does the calculation of POSITIONS).
NOTE: returns "BORDER_BOX" sizes, not BOX sizes nor CONTENT_BOX sizes, nor MARGIN_BOX sizes
NOTE: changes compared to previous algorithms: the
Parameters
FlexContainer | container | The FlexContainer you want to layout the contents of |
CSSAvailableSpace2 | availableSpaceInsideContainer | Pre-calculated value as per Spec 9.2.2 |
DefiniteContainingBlockSize | containersSizeAsAContainingBlock | Null unless the supplied FlexContainer has ALREADY been sized during this call-stack, and has a known, fixed, size-as-a-ContainingBlock |
Returns
(T1, T2)<List<FlexLine>, Dictionary<FlexItem, Vector2>> |
Overrides
Exceptions
Exception |
- ((T1, T2)<List<FlexLine>, Dictionary<FlexItem, Vector2>>) Phase1CalculateChildSizes(FlexContainer, CSSContainerSize, CSSAvailableSpace)
Parameters
FlexContainer | container | |
CSSContainerSize | containersNewSize | |
CSSAvailableSpace | whenSizeUnknown_parentsAvailableSpace |
Returns
(T1, T2)<List<FlexLine>, Dictionary<FlexItem, Vector2>> |
Overrides
- (CSSAvailableSpace2)
Spec_9_2_2_AvailableSpaceWithinContainer(FlexContainer, CSSConstrainedDimensions, CSSAvailableSpace2, Nullable, Boolean)
Spec: 9.2.2 https://www.w3.org/TR/css-flexbox-1/#algo-available
NB: we MUST diverge from the spec here a little - basic FlexContainers can follow the Spec but by definition our RootFlexContainers (which contain the special-case handling for 'how we integrate with the wider Unity UI ecosystem') must be doing something a little different - they have to take into account UnityUI.
TODO: potential minor optimization: for the first part "if that dimension ... is a definite size" could calculate both dimensions at once (since the main cost is the recursion) and we KNOW that both need to be calculated, since that's the first step for each.
Parameters
FlexContainer | container | |
CSSConstrainedDimensions | constraints | |
CSSAvailableSpace2 | availableSpaceInsideContainersContainer | NOTE: this is not the CONTAINER'S available space, it's the available space of the CONTAINER'S CONTAINER (i.e. the container's ContainingBlock, i.e. 'the space that the Container itself is being laid-out into') |
Nullable<Vector2> | preCalculatedKnownPaddingSizeOfContainer | |
Boolean | recursivelyInvoked_ShowDebugMessages |
Returns
CSSAvailableSpace2 |
Overrides
Exceptions
Exception |
- (Dictionary<FlexItem, BoxLength>)
Spec_9_2_LineLengthsDetermination(FlexContainer, List, CSSAvailableSpace2, DefiniteContainingBlockSize)
Parameters
FlexContainer | parent | |
List<FlexItem> | childItems | |
CSSAvailableSpace2 | parentsAvailableSpace | How much space the parent has for sizing/positioning its children into - will be the parents own contentbox size eventually |
DefiniteContainingBlockSize | parentsSizeAsAContainingBlock |
Returns
Dictionary<FlexItem, BoxLength> |
- (List<FlexLine>)
Spec_9_3_5_CollectIntoFlexLines(FlexContainer, List, Dictionary, ContentLength)
Spec: https://www.w3.org/TR/css-flexbox-1/#algo-line-break
NOTE: requires a special ContentLength that has been pre-converted to "0" for MIN_CONTENT, or "null" for MAX_CONTENT (where most code uses null for BOTH of those)
Parameters
FlexContainer | container | |
List<FlexItem> | childItems | |
Dictionary<FlexItem, Single> | childOuterHypotheticalLengths | |
ContentLength | spaceOrInfiniteOrZero |
Returns
List<FlexLine> |
- (Dictionary<FlexItem, BoxLength>)
Spec_9_3_6_ResolveFlexibleLengths(FlexContainer, List, Dictionary, ContentLength)
Spec: https://www.w3.org/TR/css-flexbox-1/#algo-flex
9.3.6: "Resolve the flexible lengths of all the flex items to find their used main size." NB: this delegates 100% to 9.7 (yes, the numbering is weird in the specification itself!)
NOTE: requires a special ContentLength that has been pre-converted to "0" for MIN_CONTENT, or "null" for MAX_CONTENT (where most code uses null for BOTH of those)
Parameters
FlexContainer | container | |
List<FlexLine> | flexLines | |
Dictionary<FlexItem, BoxLength> | childBoxLengths | |
ContentLength | availableMainLength |
Returns
Dictionary<FlexItem, BoxLength> |
- ((T1, T2)<List<FlexLine>, Dictionary<FlexItem, BoxLength>>)
Spec_9_3_FinalizeMainLengths(FlexContainer, List, CSSAvailableLength, Dictionary)
Spec: 9.3: https://www.w3.org/TR/css-flexbox-1/#main-sizing
NOTE: this needs the CONSTRAINED content-length from container's availablespace - it has special handling for MAX_CONTENT vs MIN_CONTENT (they are not treated the same)
W3C specification is bad here, they mean "Main Size of ChildItems Finalization" - they've already explicitly calculated the 'Main Size' both of the container AND of the child items, so their name is obviously wrong! But this is what they intended
This section returns all the info about the flex-lines that items were collected into, how long they are what is in each one, etc.
Number of lines returned will be precisely 1 if NOWRAP, and 1 or more if WRAP
Parameters
FlexContainer | container | |
List<FlexItem> | childItems | |
CSSAvailableLength | containerConstrainedInnerLength | If this is MAX_CONTENT, MIN_CONTENT, or INFINITE .. percentage sizes of child items will be treated as "0" |
Dictionary<FlexItem, BoxLength> | childHypotheticalMain_BoxLengths |
Returns
(T1, T2)<List<FlexLine>, Dictionary<FlexItem, BoxLength>> |
Exceptions
Exception |
- (Dictionary<FlexItem, BoxLength>)
Spec_9_4_11(FlexContainer, List, Dictionary, CSSAvailableLength)
Spec: 9.4.11 https://www.w3.org/TR/css-flexbox-1/#algo-stretch
"Determine the used cross size of each flex item [handling STRETCH etc]"
Parameters
FlexContainer | container | |
List<FlexLine> | flexLines | |
Dictionary<FlexItem, BoxLength> | childHypotheticalCrossLengths | |
CSSAvailableLength | containerCrossLengthForPercentages |
Returns
Dictionary<FlexItem, BoxLength> |
- (void)
Spec_9_4_8_1(AlignItems, List, out List)
Spec: 9.4.8.1 https://www.w3.org/TR/css-flexbox-1/#algo-cross-line
------- NOTE: this requires --inline-- axis, which is defined ONLY based on Latin-vs-Japanese/Chinese/etc writing mode --------
Collect all the flex items whose inline-axis is parallel to the main-axis, whose align-self is baseline, and whose cross-axis margins are both non-auto. Find the largest of the distances between each item’s baseline and its hypothetical outer cross-start edge, and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, and sum these two values. Among all the items not collected by the previous step, find the largest outer hypothetical cross size. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero. If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically."
Parameters
AlignItems | containerAlignItems | |
List<FlexItem> | itemsInLine | |
List<FlexItem> | unprocessedItems |
- (void)
Spec_9_4_8_CalculateFlexLinesCrossSizes(FlexContainer, FlexItem, List, Dictionary, CSSAvailableLength, DefiniteContainingBlockSize)
Spec 9.4.8: https://www.w3.org/TR/css-flexbox-1/#algo-cross-line
"Calculate the cross size of each flex line."
The Spec authors create a non-defined concept of "a flexline", and then never state what the "size" is of such a thing - is it a box-size, a content-size, a border-size? - leaving it ambiguous how we should later interpret that "size" during sizing and layout.
By implication, what they failed to write is: "the size of a flexline is an outer-size (ie its a MARGIN_BOX size)", since one of their statements is to calculate it from "the container's inner cross size", and the inner-size of a parent is defined as matching the outer-size of its contents.
Parameters
FlexContainer | container | |
FlexItem | containerAsItem | |
List<FlexLine> | flexLines | |
Dictionary<FlexItem, BoxLength> | childHypotheticalCrossLengths | |
CSSAvailableLength | containerAvailableCrossLength_forConversions | |
DefiniteContainingBlockSize | containersSizeAsAContainingBlock |
- (Nullable<Single>) Spec_9_4_8_ClampLineCrossSizeToContainersInnerMinMaxSizes(FlexItem, ContentLength, Axis)
VERY ANNOYINGLY the CSS-Flexbox spec never clearly defines the "cross size" of a "line" -- a line is not a Box or any such thing, just floating undefined.
InnerContentLengthOfContainerIfAlreadyKnown(FlexItem, Nullable<Single>, Axis) - this simpler version is used much later, during Spec 9.4.8.3, when an edge-case needs to "clamp the line’s cross-size to be within the container’s computed min and max cross sizes"
Parameters
FlexItem | containerAsItem | |
ContentLength | contentLength | |
Axis | axis |
Returns
Nullable<Single> | NOTE: this should be an OUTER size (it will be interpreted as such by callers) |
- (void)
Spec_9_4_9_ExpandFlexLinesIfStretch(FlexContainer, FlexItem, List, CSSAvailableLength, DefiniteContainingBlockSize)
Spec 9.4.9: https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch
As per Spec_9_4_8_CalculateFlexLinesCrossSizes(FlexContainer, FlexItem, List<FlexLine>, Dictionary<FlexItem, BoxLength>, CSSAvailableLength, DefiniteContainingBlockSize), we treat the flexlines 'sizes' as 'outer sizes'.
"Handle 'align-content: stretch'"
Spec is wrong here: they say the only thing that needs to be definite is "container ... cross size", but their algorithm ALSO requires that the "container ... inner cross size" is definite (which is NOT GUARANTEED by the constraint the Spec authors wrote down - you can have a %age padding (non-definite) with a pixel width (definite)).
AS A RESULT: if the inner-length can't be directly calculated, we throw an exception (which you can disable if you're happy with diverging from spec)
Parameters
FlexContainer | container | |
FlexItem | containerAsItem | |
List<FlexLine> | lines | |
CSSAvailableLength | availableLengthInContainer | |
DefiniteContainingBlockSize | containersSizeAsAContainingBlock |
- (Dictionary<FlexItem, BoxLength>)
Spec_9_4_CrossLengthsDetermination(FlexContainer, List, Dictionary, CSSAvailableSpace2, DefiniteContainingBlockSize)
Spec: 9.4: https://www.w3.org/TR/css-flexbox-1/#cross-sizing
"Determine the hypothetical cross size of each item"
NB: here we work with the box-sizes of each item (even though most of the calculations are going to HAVE to use the content-sizes, for obvious reasons -- but some will have to use-box-sizes, e.g. min-width/max-width/min-height/max-height are all defined in box-size, not content-size.
Parameters
FlexContainer | container | |
List<FlexLine> | flexLines | |
Dictionary<FlexItem, BoxLength> | childMainLengths | |
CSSAvailableSpace2 | availableSpaceInsideContainer_for947_forConversions | |
DefiniteContainingBlockSize | containersSizeAsAContainingBlock |
Returns
Dictionary<FlexItem, BoxLength> |
Exceptions
ArgumentException |
- (Dictionary<FlexItem, BoxLength>)
Spec_9_7_ResolveFlexibleLengths_SingleLine(FlexContainer, FlexLine, Dictionary, Single, ContentLength)
Spec: https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths
NB: omits the final step - "Set each item’s used main size to its target main size" - instead returning the 'target main sizes' and leaving it up to the caller to do the 'set' call.
Parameters
FlexContainer | container | |
FlexLine | flexLine | |
Dictionary<FlexItem, BoxLength> | childBoxLengths | |
Single | leftoverSpace | |
ContentLength | containerInnerMainLength |
Returns
Dictionary<FlexItem, BoxLength> |
- (Boolean) xIsDefiniteSize(ContentLength, out Single)
Ideally you should not be using this, but there is one place where the CSSContentLengthConstrainable has already
been converted down, using .definiteValue, and so its cleaner for that code to call this variant on
Parameters
ContentLength | length | |
Single | definiteValue |
Returns
Boolean |
- (Boolean) xIsDefiniteSize(CSSContainerLength, out Single)
- (Boolean) xIsDefiniteSize(CSSContentLengthConstrainable, out Single)
This is used in two places where the Spec says "if container's cross-size is definite", but the Spec is wrong: the cross-size is NOT definite (according to CSS: "can be determined without performing layout" - https://www.w3.org/TR/css-sizing-3/#definite) in either place but it might be known in each place because the layout needed to determine it has ALREADY been performed DURING this layout-execution. We treat that case "as if" it were definite - and the browser vendors appear to do the same thing, so we assume it is the Spec that is wrong (perhaps the authors didn't read the CSS-Sizing-3 Spec closely enough, and used 'definite' wrongly - but it's hard to tell since 'definite' is so badly defined even there, and Flexbox authors chose to explictly REDEFINE it in a semi-proprietary fashion too!).
Parameters
CSSContentLengthConstrainable | length | |
Single | definiteValue |
Returns
Boolean |