tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 9TOML 3CSS 1345 Definiciones

⎇ 70139109655efbe34ab594b10f3db52fd40f08e7 src/flexbox.rs Rust · 1897 líneas · 81.5 KB Historia Quién tocó qué crudo

Move flex-item and flex-line layout passes to methods instead of closures (#209) 701391096 · Andreas Weibye ·

1//! Computes the [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) layout algorithm on [`Taffy`](crate::Taffy) according to the [spec](https://www.w3.org/TR/css-flexbox-1/)
2//!
3//! Note that some minor steps appear to be missing: see https://github.com/DioxusLabs/taffy/issues for more information.
4use core::f32;
5
6use slotmap::SlotMap;
7
8use crate::data::NodeData;
9use crate::geometry::{Point, Rect, Size};
10use crate::layout::{Cache, Layout};
11use crate::math::MaybeMath;
12use crate::node::{MeasureFunc, Node};
13use crate::resolve::{MaybeResolve, ResolveOrDefault};
14use crate::style::{AlignContent, AlignSelf, Dimension, Display, FlexWrap, JustifyContent, PositionType};
15use crate::style::{FlexDirection, FlexboxLayout};
16use crate::sys::{abs, round, ChildrenVec, Vec};
17use crate::Taffy;
18
19/// The intermediate results of a flexbox calculation for a single item
20struct FlexItem {
21    /// The identifier for the associated [`Node`](crate::node::Node)
22    node: Node,
23
24    /// The base size of this item
25    size: Size<Option<f32>>,
26    /// The minimum allowable size of this item
27    min_size: Size<Option<f32>>,
28    /// The maximum allowable size of this item
29    max_size: Size<Option<f32>>,
30
31    /// The final offset of this item
32    position: Rect<Option<f32>>,
33    /// The margin of this item
34    margin: Rect<f32>,
35    /// The padding of this item
36    padding: Rect<f32>,
37    /// The border of this item
38    border: Rect<f32>,
39
40    /// The default size of this item
41    flex_basis: f32,
42    /// The default size of this item, minus padding and border
43    inner_flex_basis: f32,
44    /// The amount by which this item has deviated from its target size
45    violation: f32,
46    /// Is the size of this item locked
47    frozen: bool,
48
49    /// The proposed inner size of this item
50    hypothetical_inner_size: Size<f32>,
51    /// The proposed outer size of this item
52    hypothetical_outer_size: Size<f32>,
53    /// The size that this item wants to be
54    target_size: Size<f32>,
55    /// The size that this item wants to be, plus any padding and border
56    outer_target_size: Size<f32>,
57
58    /// The position of the bottom edge of this item
59    baseline: f32,
60
61    /// A temporary value for the main offset
62    ///
63    /// Offset is the relative position from the item's natural flow position based on
64    /// relative position values, alignment, and justification. Does not include margin/padding/border.
65    offset_main: f32,
66    /// A temporary value for the cross offset
67    ///
68    /// Offset is the relative position from the item's natural flow position based on
69    /// relative position values, alignment, and justification. Does not include margin/padding/border.
70    offset_cross: f32,
71}
72
73/// A line of [`FlexItem`] used for intermediate computation
74struct FlexLine<'a> {
75    /// The slice of items to iterate over during computation of this line
76    items: &'a mut [FlexItem],
77    /// The dimensions of the cross-axis
78    cross_size: f32,
79    /// The relative offset of the cross-axis
80    offset_cross: f32,
81}
82
83/// Values that can be cached during the flexbox algorithm
84struct AlgoConstants {
85    /// The direction of the current segment being laid out
86    dir: FlexDirection,
87    /// Is this segment a row
88    is_row: bool,
89    /// Is this segment a column
90    is_column: bool,
91    /// Is the wrap direction inverted
92    is_wrap_reverse: bool,
93
94    /// The margin of this section
95    margin: Rect<f32>,
96    /// The border of this section
97    border: Rect<f32>,
98    /// The padding of this section
99    padding_border: Rect<f32>,
100
101    /// The size of the internal node
102    node_inner_size: Size<Option<f32>>,
103    /// The size of the surrounding container
104    container_size: Size<f32>,
105    /// The size of the internal container
106    inner_container_size: Size<f32>,
107}
108
109impl Taffy {
110    /// Computes the layout of [`Taffy`] according to the flexbox algorithm
111    pub(crate) fn compute(&mut self, root: Node, size: Size<Option<f32>>) {
112        let style = self.nodes[root].style;
113        let has_root_min_max = style.min_size.width.is_defined()
114            || style.min_size.height.is_defined()
115            || style.max_size.width.is_defined()
116            || style.max_size.height.is_defined();
117
118        let preliminary_size = if has_root_min_max {
119            let first_pass = self.compute_preliminary(root, style.size.maybe_resolve(size), size, false, true);
120
121            self.compute_preliminary(
122                root,
123                Size {
124                    width: first_pass
125                        .width
126                        .maybe_max(style.min_size.width.maybe_resolve(size.width))
127                        .maybe_min(style.max_size.width.maybe_resolve(size.width))
128                        .into(),
129                    height: first_pass
130                        .height
131                        .maybe_max(style.min_size.height.maybe_resolve(size.height))
132                        .maybe_min(style.max_size.height.maybe_resolve(size.height))
133                        .into(),
134                },
135                size,
136                true,
137                true,
138            )
139        } else {
140            self.compute_preliminary(root, style.size.maybe_resolve(size), size, true, true)
141        };
142
143        self.nodes[root].layout = Layout { order: 0, size: preliminary_size, location: Point::ZERO };
144
145        Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0);
146    }
147
148    /// Rounds the calculated [`NodeData`] according to the spec
149    fn round_layout(
150        nodes: &mut SlotMap<Node, NodeData>,
151        children: &SlotMap<Node, ChildrenVec<Node>>,
152        root: Node,
153        abs_x: f32,
154        abs_y: f32,
155    ) {
156        let layout = &mut nodes[root].layout;
157        let abs_x = abs_x + layout.location.x;
158        let abs_y = abs_y + layout.location.y;
159
160        layout.location.x = round(layout.location.x);
161        layout.location.y = round(layout.location.y);
162
163        layout.size.width = round(layout.size.width);
164        layout.size.height = round(layout.size.height);
165
166        for child in &children[root] {
167            Self::round_layout(nodes, children, *child, abs_x, abs_y);
168        }
169    }
170
171    /// Saves intermediate results to a [`Cache`]
172    fn cache(&mut self, node: Node, main_size: bool) -> &mut Option<Cache> {
173        if main_size {
174            &mut self.nodes[node].main_size_layout_cache
175        } else {
176            &mut self.nodes[node].other_layout_cache
177        }
178    }
179
180    /// Try to get the computation result from the cache.
181    #[inline]
182    fn compute_from_cache(
183        &mut self,
184        node: Node,
185        node_size: Size<Option<f32>>,
186        parent_size: Size<Option<f32>>,
187        perform_layout: bool,
188        main_size: bool,
189    ) -> Option<Size<f32>> {
190        if let Some(ref cache) = self.cache(node, main_size) {
191            if cache.perform_layout || !perform_layout {
192                let width_compatible = if let Some(width) = node_size.width {
193                    abs(width - cache.size.width) < f32::EPSILON
194                } else {
195                    cache.node_size.width.is_none()
196                };
197
198                let height_compatible = if let Some(height) = node_size.height {
199                    abs(height - cache.size.height) < f32::EPSILON
200                } else {
201                    cache.node_size.height.is_none()
202                };
203
204                if width_compatible && height_compatible {
205                    return Some(cache.size);
206                }
207
208                if cache.node_size == node_size && cache.parent_size == parent_size {
209                    return Some(cache.size);
210                }
211            }
212        }
213
214        None
215    }
216
217    /// Compute constants that can be reused during the flexbox algorithm.
218    #[inline]
219    fn compute_constants(
220        node: &NodeData,
221        node_size: Size<Option<f32>>,
222        parent_size: Size<Option<f32>>,
223    ) -> AlgoConstants {
224        let dir = node.style.flex_direction;
225        let is_row = dir.is_row();
226        let is_column = dir.is_column();
227        let is_wrap_reverse = node.style.flex_wrap == FlexWrap::WrapReverse;
228
229        let margin = node.style.margin.resolve_or_default(parent_size.width);
230        let padding = node.style.padding.resolve_or_default(parent_size.width);
231        let border = node.style.border.resolve_or_default(parent_size.width);
232
233        let padding_border = Rect {
234            start: padding.start + border.start,
235            end: padding.end + border.end,
236            top: padding.top + border.top,
237            bottom: padding.bottom + border.bottom,
238        };
239
240        let node_inner_size = Size {
241            width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
242            height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
243        };
244
245        let container_size = Size::ZERO;
246        let inner_container_size = Size::ZERO;
247
248        AlgoConstants {
249            dir,
250            is_row,
251            is_column,
252            is_wrap_reverse,
253            margin,
254            border,
255            padding_border,
256            node_inner_size,
257            container_size,
258            inner_container_size,
259        }
260    }
261
262    /// Generate anonymous flex items.
263    ///
264    /// # [9.1. Initial Setup](https://www.w3.org/TR/css-flexbox-1/#box-manip)
265    ///
266    /// - [**Generate anonymous flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-anon-box) as described in [§4 Flex Items](https://www.w3.org/TR/css-flexbox-1/#flex-items).
267    #[inline]
268    fn generate_anonymous_flex_items(&self, node: Node, constants: &AlgoConstants) -> Vec<FlexItem> {
269        self.children[node]
270            .iter()
271            .map(|child| (child, &self.nodes[*child].style))
272            .filter(|(_, style)| style.position_type != PositionType::Absolute)
273            .filter(|(_, style)| style.display != Display::None)
274            .map(|(child, child_style)| FlexItem {
275                node: *child,
276                size: child_style.size.maybe_resolve(constants.node_inner_size),
277                min_size: child_style.min_size.maybe_resolve(constants.node_inner_size),
278                max_size: child_style.max_size.maybe_resolve(constants.node_inner_size),
279
280                position: child_style.position.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
281                margin: child_style.margin.resolve_or_default(constants.node_inner_size.width),
282                padding: child_style.padding.resolve_or_default(constants.node_inner_size.width),
283                border: child_style.border.resolve_or_default(constants.node_inner_size.width),
284                flex_basis: 0.0,
285                inner_flex_basis: 0.0,
286                violation: 0.0,
287                frozen: false,
288
289                hypothetical_inner_size: Size::ZERO,
290                hypothetical_outer_size: Size::ZERO,
291                target_size: Size::ZERO,
292                outer_target_size: Size::ZERO,
293
294                baseline: 0.0,
295
296                offset_main: 0.0,
297                offset_cross: 0.0,
298            })
299            .collect()
300    }
301
302    /// Determine the available main and cross space for the flex items.
303    ///
304    /// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
305    ///
306    /// - [**Determine the available main and cross space for the flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-available).
307    /// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
308    /// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
309    /// otherwise, subtract the flex container’s margin, border, and padding from the space available to the flex container in that dimension and use that value.
310    /// **This might result in an infinite value**.
311    #[inline]
312    #[must_use]
313    fn determine_available_space(
314        node_size: Size<Option<f32>>,
315        parent_size: Size<Option<f32>>,
316        constants: &AlgoConstants,
317    ) -> Size<Option<f32>> {
318        let width = match node_size.width {
319            Some(node_width) => Some(node_width),
320            None => parent_size
321                .width
322                .maybe_sub(constants.margin.horizontal_axis_sum())
323                .maybe_sub(constants.padding_border.horizontal_axis_sum()),
324        };
325
326        let height = match node_size.height {
327            Some(node_height) => Some(node_height),
328            None => parent_size
329                .height
330                .maybe_sub(constants.margin.vertical_axis_sum())
331                .maybe_sub(constants.padding_border.vertical_axis_sum()),
332        };
333
334        Size { width, height }
335    }
336
337    /// Determine the flex base size and hypothetical main size of each item.
338    ///
339    /// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
340    ///
341    /// - [**Determine the flex base size and hypothetical main size of each item:**](https://www.w3.org/TR/css-flexbox-1/#algo-main-item)
342    ///
343    ///     - A. If the item has a definite used flex basis, that’s the flex base size.
344    ///
345    ///     - B. If the flex item has ...
346    ///
347    ///         - an intrinsic aspect ratio,
348    ///         - a used flex basis of content, and
349    ///         - a definite cross size,
350    ///
351    ///     then the flex base size is calculated from its inner cross size and the flex item’s intrinsic aspect ratio.
352    ///
353    ///     - C. If the used flex basis is content or depends on its available space, and the flex container is being sized under a min-content
354    ///         or max-content constraint (e.g. when performing automatic table layout \[CSS21\]), size the item under that constraint.
355    ///         The flex base size is the item’s resulting main size.
356    ///
357    ///     - E. Otherwise, size the item into the available space using its used flex basis in place of its main size, treating a value of content as max-content.
358    ///         If a cross size is needed to determine the main size (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite,
359    ///         in this calculation use fit-content as the flex item’s cross size. The flex base size is the item’s resulting main size.
360    ///
361    ///     When determining the flex base size, the item’s min and max main sizes are ignored (no clamping occurs).
362    ///     Furthermore, the sizing calculations that floor the content box size at zero when applying box-sizing are also ignored.
363    ///     (For example, an item with a specified size of zero, positive padding, and box-sizing: border-box will have an outer flex base size of zero—and hence a negative inner flex base size.)
364    #[inline]
365    fn determine_flex_base_size(
366        &mut self,
367        node: Node,
368        node_size: Size<Option<f32>>,
369        constants: &AlgoConstants,
370        available_space: Size<Option<f32>>,
371        flex_items: &mut Vec<FlexItem>,
372    ) {
373        // TODO - this does not follow spec. See the TODOs below
374        for child in flex_items.iter_mut() {
375            let child_style = self.nodes[child.node].style;
376
377            // A. If the item has a definite used flex basis, that’s the flex base size.
378
379            let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
380            if flex_basis.is_some() {
381                child.flex_basis = flex_basis.unwrap_or(0.0);
382                continue;
383            };
384
385            // B. If the flex item has an intrinsic aspect ratio,
386            //    a used flex basis of content, and a definite cross size,
387            //    then the flex base size is calculated from its inner
388            //    cross size and the flex item’s intrinsic aspect ratio.
389
390            if let Some(ratio) = child_style.aspect_ratio {
391                if let Some(cross) = node_size.cross(constants.dir) {
392                    if child_style.flex_basis == Dimension::Auto {
393                        child.flex_basis = cross * ratio;
394                        continue;
395                    }
396                }
397            }
398
399            // C. If the used flex basis is content or depends on its available space,
400            //    and the flex container is being sized under a min-content or max-content
401            //    constraint (e.g. when performing automatic table layout [CSS21]),
402            //    size the item under that constraint. The flex base size is the item’s
403            //    resulting main size.
404
405            // TODO - Probably need to cover this case in future
406
407            // D. Otherwise, if the used flex basis is content or depends on its
408            //    available space, the available main size is infinite, and the flex item’s
409            //    inline axis is parallel to the main axis, lay the item out using the rules
410            //    for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
411            //    is the item’s max-content main size.
412
413            // TODO - Probably need to cover this case in future
414
415            // E. Otherwise, size the item into the available space using its used flex basis
416            //    in place of its main size, treating a value of content as max-content.
417            //    If a cross size is needed to determine the main size (e.g. when the
418            //    flex item’s main size is in its block axis) and the flex item’s cross size
419            //    is auto and not definite, in this calculation use fit-content as the
420            //    flex item’s cross size. The flex base size is the item’s resulting main size.
421
422            let width: Option<f32> = if child.size.width.is_none()
423                && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
424                && constants.is_column
425            {
426                available_space.width
427            } else {
428                child.size.width
429            };
430
431            let height: Option<f32> = if child.size.height.is_none()
432                && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
433                && constants.is_row
434            {
435                available_space.height
436            } else {
437                child.size.height
438            };
439
440            child.flex_basis = self
441                .compute_preliminary(
442                    child.node,
443                    Size {
444                        width: width.maybe_min(child.max_size.width),
445                        height: height.maybe_min(child.max_size.height),
446                    },
447                    available_space,
448                    false,
449                    true,
450                )
451                .main(constants.dir)
452                .maybe_min(child.max_size.main(constants.dir));
453        }
454
455        // The hypothetical main size is the item’s flex base size clamped according to its
456        // used min and max main sizes (and flooring the content box size at zero).
457
458        for child in flex_items {
459            child.inner_flex_basis = child.flex_basis
460                - child.padding.main_axis_sum(constants.dir)
461                - child.border.main_axis_sum(constants.dir);
462
463            // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
464            // The following logic was developed not from the spec but by trail and error looking into how
465            // webkit handled various scenarios. Can probably be solved better by passing in
466            // min-content max-content constraints from the top
467            let min_main = self
468                .compute_preliminary(child.node, Size::NONE, available_space, false, false)
469                .main(constants.dir)
470                .maybe_max(child.min_size.main(constants.dir))
471                .maybe_min(child.size.main(constants.dir))
472                .into();
473
474            child.hypothetical_inner_size.set_main(
475                constants.dir,
476                child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(constants.dir)),
477            );
478
479            child.hypothetical_outer_size.set_main(
480                constants.dir,
481                child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
482            );
483        }
484    }
485
486    /// Collect flex items into flex lines.
487    ///
488    /// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
489    ///
490    /// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
491    ///
492    ///     - If the flex container is single-line, collect all the flex items into a single flex line.
493    ///
494    ///     - Otherwise, starting from the first uncollected item, collect consecutive items one by one until the first time that the next collected item would not fit into the flex container’s inner main size
495    ///         (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
496    ///         If the very first uncollected item wouldn't fit, collect just it into the line.
497    ///
498    ///         For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
499    ///
500    ///         Repeat until all flex items have been collected into flex lines.
501    ///
502    ///         **Note that the "collect as many" line will collect zero-sized flex items onto the end of the previous line even if the last non-zero item exactly "filled up" the line**.
503    #[inline]
504    fn collect_flex_lines<'a>(
505        &self,
506        node: Node,
507        constants: &AlgoConstants,
508        available_space: Size<Option<f32>>,
509        flex_items: &'a mut Vec<FlexItem>,
510    ) -> Vec<FlexLine<'a>> {
511        let mut lines = crate::sys::new_vec_with_capacity(1);
512
513        if self.nodes[node].style.flex_wrap == FlexWrap::NoWrap {
514            lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
515        } else {
516            let mut flex_items = &mut flex_items[..];
517
518            while !flex_items.is_empty() {
519                let mut line_length = 0.0;
520                let index = flex_items
521                    .iter()
522                    .enumerate()
523                    .find(|&(idx, child)| {
524                        line_length += child.hypothetical_outer_size.main(constants.dir);
525                        if let Some(main) = available_space.main(constants.dir) {
526                            line_length > main && idx != 0
527                        } else {
528                            false
529                        }
530                    })
531                    .map(|(idx, _)| idx)
532                    .unwrap_or(flex_items.len());
533
534                let (items, rest) = flex_items.split_at_mut(index);
535                lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
536                flex_items = rest;
537            }
538        }
539
540        lines
541    }
542
543    /// Resolve the flexible lengths of the items within a flex line.
544    ///
545    /// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
546    #[inline]
547    fn resolve_flexible_lengths(
548        &mut self,
549        line: &mut FlexLine,
550        constants: &AlgoConstants,
551        available_space: Size<Option<f32>>,
552    ) {
553        // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
554        //    items on the line. If the sum is less than the flex container’s inner main size,
555        //    use the flex grow factor for the rest of this algorithm; otherwise, use the
556        //    flex shrink factor.
557
558        let used_flex_factor: f32 =
559            line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum();
560        let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
561        let shrinking = !growing;
562
563        // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
564        //    - Any item that has a flex factor of zero
565        //    - If using the flex grow factor: any item that has a flex base size
566        //      greater than its hypothetical main size
567        //    - If using the flex shrink factor: any item that has a flex base size
568        //      smaller than its hypothetical main size
569
570        for child in line.items.iter_mut() {
571            // TODO - This is not found by reading the spec. Maybe this can be done in some other place
572            // instead. This was found by trail and error fixing tests to align with webkit output.
573            if constants.node_inner_size.main(constants.dir).is_none() && constants.is_row {
574                child.target_size.set_main(
575                    constants.dir,
576                    self.compute_preliminary(
577                        child.node,
578                        Size {
579                            width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
580                            height: child.size.height.maybe_max(child.min_size.height).maybe_min(child.max_size.height),
581                        },
582                        available_space,
583                        false,
584                        false,
585                    )
586                    .main(constants.dir)
587                    .maybe_max(child.min_size.main(constants.dir))
588                    .maybe_min(child.max_size.main(constants.dir)),
589                );
590            } else {
591                child.target_size.set_main(constants.dir, child.hypothetical_inner_size.main(constants.dir));
592            }
593
594            // TODO this should really only be set inside the if-statement below but
595            // that causes the target_main_size to never be set for some items
596
597            child.outer_target_size.set_main(
598                constants.dir,
599                child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
600            );
601
602            let child_style = &self.nodes[child.node].style;
603            if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
604                || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
605                || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
606            {
607                child.frozen = true;
608            }
609        }
610
611        // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
612        //    and subtract this from the flex container’s inner main size. For frozen items,
613        //    use their outer target main size; for other items, use their outer flex base size.
614
615        let used_space: f32 = line
616            .items
617            .iter()
618            .map(|child| {
619                child.margin.main_axis_sum(constants.dir)
620                    + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
621            })
622            .sum();
623
624        let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
625
626        // 4. Loop
627
628        loop {
629            // a. Check for flexible items. If all the flex items on the line are frozen,
630            //    free space has been distributed; exit this loop.
631
632            if line.items.iter().all(|child| child.frozen) {
633                break;
634            }
635
636            // b. Calculate the remaining free space as for initial free space, above.
637            //    If the sum of the unfrozen flex items’ flex factors is less than one,
638            //    multiply the initial free space by this sum. If the magnitude of this
639            //    value is less than the magnitude of the remaining free space, use this
640            //    as the remaining free space.
641
642            let used_space: f32 = line
643                .items
644                .iter()
645                .map(|child| {
646                    child.margin.main_axis_sum(constants.dir)
647                        + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
648                })
649                .sum();
650
651            let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
652
653            let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
654                unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
655                    let style = &self.nodes[item.node].style;
656                    (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink)
657                });
658
659            let free_space = if growing && sum_flex_grow < 1.0 {
660                (initial_free_space * sum_flex_grow)
661                    .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
662            } else if shrinking && sum_flex_shrink < 1.0 {
663                (initial_free_space * sum_flex_shrink)
664                    .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
665            } else {
666                (constants.node_inner_size.main(constants.dir).maybe_sub(used_space)).unwrap_or(0.0)
667            };
668
669            // c. Distribute free space proportional to the flex factors.
670            //    - If the remaining free space is zero
671            //        Do Nothing
672            //    - If using the flex grow factor
673            //        Find the ratio of the item’s flex grow factor to the sum of the
674            //        flex grow factors of all unfrozen items on the line. Set the item’s
675            //        target main size to its flex base size plus a fraction of the remaining
676            //        free space proportional to the ratio.
677            //    - If using the flex shrink factor
678            //        For every unfrozen item on the line, multiply its flex shrink factor by
679            //        its inner flex base size, and note this as its scaled flex shrink factor.
680            //        Find the ratio of the item’s scaled flex shrink factor to the sum of the
681            //        scaled flex shrink factors of all unfrozen items on the line. Set the item’s
682            //        target main size to its flex base size minus a fraction of the absolute value
683            //        of the remaining free space proportional to the ratio. Note this may result
684            //        in a negative inner main size; it will be corrected in the next step.
685            //    - Otherwise
686            //        Do Nothing
687
688            if free_space.is_normal() {
689                if growing && sum_flex_grow > 0.0 {
690                    for child in &mut unfrozen {
691                        child.target_size.set_main(
692                            constants.dir,
693                            child.flex_basis + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow),
694                        );
695                    }
696                } else if shrinking && sum_flex_shrink > 0.0 {
697                    let sum_scaled_shrink_factor: f32 = unfrozen
698                        .iter()
699                        .map(|child| child.inner_flex_basis * self.nodes[child.node].style.flex_shrink)
700                        .sum();
701
702                    if sum_scaled_shrink_factor > 0.0 {
703                        for child in &mut unfrozen {
704                            let scaled_shrink_factor =
705                                child.inner_flex_basis * self.nodes[child.node].style.flex_shrink;
706                            child.target_size.set_main(
707                                constants.dir,
708                                child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
709                            )
710                        }
711                    }
712                }
713            }
714
715            // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
716            //    used min and max main sizes and floor its content-box size at zero. If the
717            //    item’s target main size was made smaller by this, it’s a max violation.
718            //    If the item’s target main size was made larger by this, it’s a min violation.
719
720            let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
721                // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
722                // The following logic was developed not from the spec but by trial and error looking into how
723                // webkit handled various scenarios. Can probably be solved better by passing in
724                // min-content max-content constraints from the top. Need to figure out correct thing to do here as
725                // just piling on more conditionals.
726                let min_main = if constants.is_row && self.nodes[child.node].measure.is_none() {
727                    self.compute_preliminary(child.node, Size::NONE, available_space, false, false)
728                        .width
729                        .maybe_min(child.size.width)
730                        .maybe_max(child.min_size.width)
731                        .into()
732                } else {
733                    child.min_size.main(constants.dir)
734                };
735
736                let max_main = child.max_size.main(constants.dir);
737                let clamped = child.target_size.main(constants.dir).maybe_min(max_main).maybe_max(min_main).max(0.0);
738                child.violation = clamped - child.target_size.main(constants.dir);
739                child.target_size.set_main(constants.dir, clamped);
740                child.outer_target_size.set_main(
741                    constants.dir,
742                    child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
743                );
744
745                acc + child.violation
746            });
747
748            // e. Freeze over-flexed items. The total violation is the sum of the adjustments
749            //    from the previous step ∑(clamped size - unclamped size). If the total violation is:
750            //    - Zero
751            //        Freeze all items.
752            //    - Positive
753            //        Freeze all the items with min violations.
754            //    - Negative
755            //        Freeze all the items with max violations.
756
757            for child in &mut unfrozen {
758                match total_violation {
759                    v if v > 0.0 => child.frozen = child.violation > 0.0,
760                    v if v < 0.0 => child.frozen = child.violation < 0.0,
761                    _ => child.frozen = true,
762                }
763            }
764
765            // f. Return to the start of this loop.
766        }
767    }
768
769    /// Determine the hypothetical cross size of each item.
770    ///
771    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
772    ///
773    /// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
774    ///     by performing layout with the used main size and the available space, treating auto as fit-content.
775    #[inline]
776    fn determine_hypothetical_cross_size(
777        &mut self,
778        line: &mut FlexLine,
779        constants: &AlgoConstants,
780        available_space: Size<Option<f32>>,
781    ) {
782        for child in line.items.iter_mut() {
783            let child_cross = child
784                .size
785                .cross(constants.dir)
786                .maybe_max(child.min_size.cross(constants.dir))
787                .maybe_min(child.max_size.cross(constants.dir));
788
789            child.hypothetical_inner_size.set_cross(
790                constants.dir,
791                self.compute_preliminary(
792                    child.node,
793                    Size {
794                        width: if constants.is_row { child.target_size.width.into() } else { child_cross },
795                        height: if constants.is_row { child_cross } else { child.target_size.height.into() },
796                    },
797                    Size {
798                        width: if constants.is_row {
799                            constants.container_size.main(constants.dir).into()
800                        } else {
801                            available_space.width
802                        },
803                        height: if constants.is_row {
804                            available_space.height
805                        } else {
806                            constants.container_size.main(constants.dir).into()
807                        },
808                    },
809                    false,
810                    false,
811                )
812                .cross(constants.dir)
813                .maybe_max(child.min_size.cross(constants.dir))
814                .maybe_min(child.max_size.cross(constants.dir)),
815            );
816
817            child.hypothetical_outer_size.set_cross(
818                constants.dir,
819                child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
820            );
821        }
822    }
823
824    /// Calculate the base lines of the children.
825    #[inline]
826    fn calculate_children_base_lines(
827        &mut self,
828        node: Node,
829        node_size: Size<Option<f32>>,
830        flex_lines: &mut [FlexLine],
831        constants: &AlgoConstants,
832    ) {
833        /// Recursively calculates the baseline for children
834        fn calc_baseline(db: &Taffy, node: Node, layout: &Layout) -> f32 {
835            if db.children[node].is_empty() {
836                layout.size.height
837            } else {
838                let child = db.children[node][0];
839                calc_baseline(db, child, &db.nodes[child].layout)
840            }
841        }
842
843        for line in flex_lines {
844            for child in line.items.iter_mut() {
845                let preliminary_size = self.compute_preliminary(
846                    child.node,
847                    Size {
848                        width: if constants.is_row {
849                            child.target_size.width.into()
850                        } else {
851                            child.hypothetical_inner_size.width.into()
852                        },
853                        height: if constants.is_row {
854                            child.hypothetical_inner_size.height.into()
855                        } else {
856                            child.target_size.height.into()
857                        },
858                    },
859                    Size {
860                        width: if constants.is_row { constants.container_size.width.into() } else { node_size.width },
861                        height: if constants.is_row {
862                            node_size.height
863                        } else {
864                            constants.container_size.height.into()
865                        },
866                    },
867                    true,
868                    false,
869                );
870
871                child.baseline = calc_baseline(
872                    self,
873                    child.node,
874                    &Layout {
875                        order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
876                        size: preliminary_size,
877                        location: Point::ZERO,
878                    },
879                );
880            }
881        }
882    }
883
884    /// Calculate the cross size of each flex line.
885    ///
886    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
887    ///
888    /// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
889    ///
890    ///     If the flex container is single-line and has a definite cross size, the cross size of the flex line is the flex container’s inner cross size.
891    ///
892    ///     Otherwise, for each flex line:
893    ///
894    ///     1. 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.
895    ///         Find the largest of the distances between each item’s baseline and its hypothetical outer cross-start edge,
896    ///         and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, and sum these two values.
897    ///
898    ///     2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
899    ///
900    ///     3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero.
901    ///
902    ///         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.
903    ///         **Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically**.
904    #[inline]
905    fn calculate_cross_size(
906        &mut self,
907        flex_lines: &mut [FlexLine],
908        node: Node,
909        node_size: Size<Option<f32>>,
910        constants: &AlgoConstants,
911    ) {
912        if flex_lines.len() == 1 && node_size.cross(constants.dir).is_some() {
913            flex_lines[0].cross_size =
914                (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
915                    .unwrap_or(0.0);
916        } else {
917            for line in flex_lines.iter_mut() {
918                //    1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
919                //       align-self is baseline, and whose cross-axis margins are both non-auto. Find the
920                //       largest of the distances between each item’s baseline and its hypothetical outer
921                //       cross-start edge, and the largest of the distances between each item’s baseline
922                //       and its hypothetical outer cross-end edge, and sum these two values.
923
924                //    2. Among all the items not collected by the previous step, find the largest
925                //       outer hypothetical cross size.
926
927                //    3. The used cross-size of the flex line is the largest of the numbers found in the
928                //       previous two steps and zero.
929
930                let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
931                line.cross_size = line
932                    .items
933                    .iter()
934                    .map(|child| {
935                        let child_style = &self.nodes[child.node].style;
936                        if child_style.align_self(&self.nodes[node].style) == AlignSelf::Baseline
937                            && child_style.cross_margin_start(constants.dir) != Dimension::Auto
938                            && child_style.cross_margin_end(constants.dir) != Dimension::Auto
939                            && child_style.cross_size(constants.dir) == Dimension::Auto
940                        {
941                            max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
942                        } else {
943                            child.hypothetical_outer_size.cross(constants.dir)
944                        }
945                    })
946                    .fold(0.0, |acc, x| acc.max(x));
947            }
948        }
949    }
950
951    /// Handle 'align-content: stretch'.
952    ///
953    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
954    ///
955    /// - [**Handle 'align-content: stretch'**](https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch). If the flex container has a definite cross size, align-content is stretch,
956    ///     and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
957    ///     increase the cross size of each flex line by equal amounts such that the sum of their cross sizes exactly equals the flex container’s inner cross size.
958    #[inline]
959    fn handle_align_content_stretch(
960        &mut self,
961        flex_lines: &mut [FlexLine],
962        node: Node,
963        node_size: Size<Option<f32>>,
964        constants: &AlgoConstants,
965    ) {
966        if self.nodes[node].style.align_content == AlignContent::Stretch && node_size.cross(constants.dir).is_some() {
967            let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
968            let inner_cross =
969                (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
970                    .unwrap_or(0.0);
971
972            if total_cross < inner_cross {
973                let remaining = inner_cross - total_cross;
974                let addition = remaining / flex_lines.len() as f32;
975                flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
976            }
977        }
978    }
979
980    /// Determine the used cross size of each flex item.
981    ///
982    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
983    ///
984    /// - [**Determine the used cross size of each flex item**](https://www.w3.org/TR/css-flexbox-1/#algo-stretch). If a flex item has align-self: stretch, its computed cross size property is auto,
985    ///     and neither of its cross-axis margins are auto, the used outer cross size is the used cross size of its flex line, clamped according to the item’s used min and max cross sizes.
986    ///     Otherwise, the used cross size is the item’s hypothetical cross size.
987    ///
988    ///     If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.
989    ///
990    ///     **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
991    #[inline]
992    fn determine_used_cross_size(&mut self, flex_lines: &mut [FlexLine], node: Node, constants: &AlgoConstants) {
993        for line in flex_lines {
994            let line_cross_size = line.cross_size;
995
996            for child in line.items.iter_mut() {
997                let child_style = &self.nodes[child.node].style;
998                child.target_size.set_cross(
999                    constants.dir,
1000                    if child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
1001                        && child_style.cross_margin_start(constants.dir) != Dimension::Auto
1002                        && child_style.cross_margin_end(constants.dir) != Dimension::Auto
1003                        && child_style.cross_size(constants.dir) == Dimension::Auto
1004                    {
1005                        (line_cross_size - child.margin.cross_axis_sum(constants.dir))
1006                            .maybe_max(child.min_size.cross(constants.dir))
1007                            .maybe_min(child.max_size.cross(constants.dir))
1008                    } else {
1009                        child.hypothetical_inner_size.cross(constants.dir)
1010                    },
1011                );
1012
1013                child.outer_target_size.set_cross(
1014                    constants.dir,
1015                    child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1016                );
1017            }
1018        }
1019    }
1020
1021    /// Distribute any remaining free space.
1022    ///
1023    /// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1024    ///
1025    /// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1026    ///
1027    ///     1. If the remaining free space is positive and at least one main-axis margin on this line is `auto`, distribute the free space equally among these margins.
1028    ///         Otherwise, set all `auto` margins to zero.
1029    ///
1030    ///     2. Align the items along the main-axis per `justify-content`.
1031    #[inline]
1032    fn distribute_remaining_free_space(&mut self, flex_lines: &mut [FlexLine], node: Node, constants: &AlgoConstants) {
1033        for line in flex_lines {
1034            let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum();
1035            let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1036            let mut num_auto_margins = 0;
1037
1038            for child in line.items.iter_mut() {
1039                let child_style = &self.nodes[child.node].style;
1040                if child_style.main_margin_start(constants.dir) == Dimension::Auto {
1041                    num_auto_margins += 1;
1042                }
1043                if child_style.main_margin_end(constants.dir) == Dimension::Auto {
1044                    num_auto_margins += 1;
1045                }
1046            }
1047
1048            if free_space > 0.0 && num_auto_margins > 0 {
1049                let margin = free_space / num_auto_margins as f32;
1050
1051                for child in line.items.iter_mut() {
1052                    let child_style = &self.nodes[child.node].style;
1053                    if child_style.main_margin_start(constants.dir) == Dimension::Auto {
1054                        if constants.is_row {
1055                            child.margin.start = margin;
1056                        } else {
1057                            child.margin.top = margin;
1058                        }
1059                    }
1060                    if child_style.main_margin_end(constants.dir) == Dimension::Auto {
1061                        if constants.is_row {
1062                            child.margin.end = margin;
1063                        } else {
1064                            child.margin.bottom = margin;
1065                        }
1066                    }
1067                }
1068            } else {
1069                let num_items = line.items.len();
1070                let layout_reverse = constants.dir.is_reverse();
1071
1072                let justify_item = |(i, child): (usize, &mut FlexItem)| {
1073                    let is_first = i == 0;
1074
1075                    child.offset_main = match self.nodes[node].style.justify_content {
1076                        JustifyContent::FlexStart => {
1077                            if layout_reverse && is_first {
1078                                free_space
1079                            } else {
1080                                0.0
1081                            }
1082                        }
1083                        JustifyContent::Center => {
1084                            if is_first {
1085                                free_space / 2.0
1086                            } else {
1087                                0.0
1088                            }
1089                        }
1090                        JustifyContent::FlexEnd => {
1091                            if is_first && !layout_reverse {
1092                                free_space
1093                            } else {
1094                                0.0
1095                            }
1096                        }
1097                        JustifyContent::SpaceBetween => {
1098                            if is_first {
1099                                0.0
1100                            } else {
1101                                free_space / (num_items - 1) as f32
1102                            }
1103                        }
1104                        JustifyContent::SpaceAround => {
1105                            if is_first {
1106                                (free_space / num_items as f32) / 2.0
1107                            } else {
1108                                free_space / num_items as f32
1109                            }
1110                        }
1111                        JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
1112                    };
1113                };
1114
1115                if layout_reverse {
1116                    line.items.iter_mut().rev().enumerate().for_each(justify_item);
1117                } else {
1118                    line.items.iter_mut().enumerate().for_each(justify_item);
1119                }
1120            }
1121        }
1122    }
1123
1124    /// Resolve cross-axis `auto` margins.
1125    ///
1126    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1127    ///
1128    /// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1129    ///     If a flex item has auto cross-axis margins:
1130    ///
1131    ///     - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1132    ///         distribute the difference in those sizes equally to the auto margins.
1133    ///
1134    ///     - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1135    ///         Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1136    #[inline]
1137    fn resolve_cross_axis_auto_margins(&mut self, flex_lines: &mut [FlexLine], node: Node, constants: &AlgoConstants) {
1138        for line in flex_lines {
1139            let line_cross_size = line.cross_size;
1140            let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1141
1142            for child in line.items.iter_mut() {
1143                let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1144                let child_style = &self.nodes[child.node].style;
1145
1146                if child_style.cross_margin_start(constants.dir) == Dimension::Auto
1147                    && child_style.cross_margin_end(constants.dir) == Dimension::Auto
1148                {
1149                    if constants.is_row {
1150                        child.margin.top = free_space / 2.0;
1151                        child.margin.bottom = free_space / 2.0;
1152                    } else {
1153                        child.margin.start = free_space / 2.0;
1154                        child.margin.end = free_space / 2.0;
1155                    }
1156                } else if child_style.cross_margin_start(constants.dir) == Dimension::Auto {
1157                    if constants.is_row {
1158                        child.margin.top = free_space;
1159                    } else {
1160                        child.margin.start = free_space;
1161                    }
1162                } else if child_style.cross_margin_end(constants.dir) == Dimension::Auto {
1163                    if constants.is_row {
1164                        child.margin.bottom = free_space;
1165                    } else {
1166                        child.margin.end = free_space;
1167                    }
1168                } else {
1169                    // 14. Align all flex items along the cross-axis.
1170                    child.offset_cross = self.align_flex_items_along_cross_axis(
1171                        node,
1172                        child,
1173                        child_style,
1174                        free_space,
1175                        max_baseline,
1176                        constants,
1177                    );
1178                }
1179            }
1180        }
1181    }
1182
1183    /// Align all flex items along the cross-axis.
1184    ///
1185    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1186    ///
1187    /// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1188    ///     if neither of the item's cross-axis margins are `auto`.
1189    #[inline]
1190    fn align_flex_items_along_cross_axis(
1191        &self,
1192        node: Node,
1193        child: &mut FlexItem,
1194        child_style: &FlexboxLayout,
1195        free_space: f32,
1196        max_baseline: f32,
1197        constants: &AlgoConstants,
1198    ) -> f32 {
1199        match child_style.align_self(&self.nodes[node].style) {
1200            AlignSelf::Auto => unreachable!(),
1201            AlignSelf::FlexStart => {
1202                if constants.is_wrap_reverse {
1203                    free_space
1204                } else {
1205                    0.0
1206                }
1207            }
1208            AlignSelf::FlexEnd => {
1209                if constants.is_wrap_reverse {
1210                    0.0
1211                } else {
1212                    free_space
1213                }
1214            }
1215            AlignSelf::Center => free_space / 2.0,
1216            AlignSelf::Baseline => {
1217                if constants.is_row {
1218                    max_baseline - child.baseline
1219                } else {
1220                    // baseline alignment only makes sense if the constants.direction is row
1221                    // we treat it as flex-start alignment in columns.
1222                    if constants.is_wrap_reverse {
1223                        free_space
1224                    } else {
1225                        0.0
1226                    }
1227                }
1228            }
1229            AlignSelf::Stretch => {
1230                if constants.is_wrap_reverse {
1231                    free_space
1232                } else {
1233                    0.0
1234                }
1235            }
1236        }
1237    }
1238
1239    /// Determine the flex container’s used cross size.
1240    ///
1241    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1242    ///
1243    /// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1244    ///
1245    ///     - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1246    ///
1247    ///     - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1248    #[inline]
1249    #[must_use]
1250    fn determine_container_cross_size(
1251        flex_lines: &mut [FlexLine],
1252        node_size: Size<Option<f32>>,
1253        constants: &mut AlgoConstants,
1254    ) -> f32 {
1255        let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
1256
1257        constants.container_size.set_cross(
1258            constants.dir,
1259            node_size
1260                .cross(constants.dir)
1261                .unwrap_or(total_cross_size + constants.padding_border.cross_axis_sum(constants.dir)),
1262        );
1263
1264        constants.inner_container_size.set_cross(
1265            constants.dir,
1266            constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
1267        );
1268
1269        total_cross_size
1270    }
1271
1272    /// Align all flex lines per `align-content`.
1273    ///
1274    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1275    ///
1276    /// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1277    #[inline]
1278    fn align_flex_lines_per_align_content(
1279        &self,
1280        flex_lines: &mut [FlexLine],
1281        node: Node,
1282        constants: &AlgoConstants,
1283        total_cross_size: f32,
1284    ) {
1285        let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size;
1286        let num_lines = flex_lines.len();
1287
1288        let align_line = |(i, line): (usize, &mut FlexLine)| {
1289            let is_first = i == 0;
1290
1291            line.offset_cross = match self.nodes[node].style.align_content {
1292                AlignContent::FlexStart => {
1293                    if is_first && constants.is_wrap_reverse {
1294                        free_space
1295                    } else {
1296                        0.0
1297                    }
1298                }
1299                AlignContent::FlexEnd => {
1300                    if is_first && !constants.is_wrap_reverse {
1301                        free_space
1302                    } else {
1303                        0.0
1304                    }
1305                }
1306                AlignContent::Center => {
1307                    if is_first {
1308                        free_space / 2.0
1309                    } else {
1310                        0.0
1311                    }
1312                }
1313                AlignContent::Stretch => 0.0,
1314                AlignContent::SpaceBetween => {
1315                    if is_first {
1316                        0.0
1317                    } else {
1318                        free_space / (num_lines - 1) as f32
1319                    }
1320                }
1321                AlignContent::SpaceAround => {
1322                    if is_first {
1323                        (free_space / num_lines as f32) / 2.0
1324                    } else {
1325                        free_space / num_lines as f32
1326                    }
1327                }
1328            };
1329        };
1330
1331        if constants.is_wrap_reverse {
1332            flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1333        } else {
1334            flex_lines.iter_mut().enumerate().for_each(align_line);
1335        }
1336    }
1337
1338    /// Calculates the layout for a flex-item
1339    #[allow(clippy::too_many_arguments)]
1340    fn calculate_flex_item(
1341        &mut self,
1342        node: Node,
1343        item: &mut FlexItem,
1344        total_offset_main: &mut f32,
1345        total_offset_cross: f32,
1346        line_offset_cross: f32,
1347        container_size: Size<f32>,
1348        direction: FlexDirection,
1349    ) {
1350        let preliminary_size = self.compute_preliminary(
1351            item.node,
1352            item.target_size.map(|s| s.into()),
1353            container_size.map(|s| s.into()),
1354            true,
1355            false,
1356        );
1357
1358        let offset_main = *total_offset_main
1359            + item.offset_main
1360            + item.margin.main_start(direction)
1361            + (item.position.main_start(direction).unwrap_or(0.0) - item.position.main_end(direction).unwrap_or(0.0));
1362
1363        let offset_cross = total_offset_cross
1364            + item.offset_cross
1365            + line_offset_cross
1366            + item.margin.cross_start(direction)
1367            + (item.position.cross_start(direction).unwrap_or(0.0) - item.position.cross_end(direction).unwrap_or(0.0));
1368
1369        self.nodes[item.node].layout = Layout {
1370            order: self.children[node].iter().position(|n| *n == item.node).unwrap() as u32,
1371            size: preliminary_size,
1372            location: Point {
1373                x: if direction.is_row() { offset_main } else { offset_cross },
1374                y: if direction.is_column() { offset_main } else { offset_cross },
1375            },
1376        };
1377
1378        *total_offset_main +=
1379            item.offset_main + item.margin.main_axis_sum(direction) + preliminary_size.main(direction);
1380    }
1381
1382    /// Calculates the layout line
1383    fn calculate_layout_line(
1384        &mut self,
1385        node: Node,
1386        line: &mut FlexLine,
1387        total_offset_cross: &mut f32,
1388        container_size: Size<f32>,
1389        padding_border: Rect<f32>,
1390        direction: FlexDirection,
1391    ) {
1392        let mut total_offset_main = padding_border.main_start(direction);
1393        let line_offset_cross = line.offset_cross;
1394
1395        if direction.is_reverse() {
1396            for item in line.items.iter_mut().rev() {
1397                self.calculate_flex_item(
1398                    node,
1399                    item,
1400                    &mut total_offset_main,
1401                    *total_offset_cross,
1402                    line_offset_cross,
1403                    container_size,
1404                    direction,
1405                );
1406            }
1407        } else {
1408            for item in line.items.iter_mut() {
1409                self.calculate_flex_item(
1410                    node,
1411                    item,
1412                    &mut total_offset_main,
1413                    *total_offset_cross,
1414                    line_offset_cross,
1415                    container_size,
1416                    direction,
1417                );
1418            }
1419        }
1420
1421        *total_offset_cross += line_offset_cross + line.cross_size;
1422    }
1423
1424    /// Do a final layout pass and collect the resulting layouts.
1425    #[inline]
1426    fn final_layout_pass(&mut self, node: Node, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1427        let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
1428
1429        if constants.is_wrap_reverse {
1430            for line in flex_lines.iter_mut().rev() {
1431                self.calculate_layout_line(
1432                    node,
1433                    line,
1434                    &mut total_offset_cross,
1435                    constants.container_size,
1436                    constants.padding_border,
1437                    constants.dir,
1438                );
1439            }
1440        } else {
1441            for line in flex_lines.iter_mut() {
1442                self.calculate_layout_line(
1443                    node,
1444                    line,
1445                    &mut total_offset_cross,
1446                    constants.container_size,
1447                    constants.padding_border,
1448                    constants.dir,
1449                );
1450            }
1451        }
1452    }
1453
1454    /// Perform absolute layout on all absolutely positioned children.
1455    #[inline]
1456    fn perform_absolute_layout_on_absolute_children(&mut self, node: Node, constants: &AlgoConstants) {
1457        // TODO: remove number of Vec<_> generated
1458        let candidates = self.children[node]
1459            .iter()
1460            .cloned()
1461            .enumerate()
1462            .filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute)
1463            .collect::<Vec<_>>();
1464
1465        for (order, child) in candidates {
1466            let container_width = constants.container_size.width.into();
1467            let container_height = constants.container_size.height.into();
1468
1469            let child_style = self.nodes[child].style;
1470
1471            // X-axis
1472            let child_position_start = child_style.position.start.maybe_resolve(container_width);
1473            let child_margin_start = child_style.margin.start.maybe_resolve(container_width);
1474            let start = child_position_start.maybe_add(child_margin_start);
1475
1476            let child_position_end = child_style.position.end.maybe_resolve(container_width);
1477            let child_margin_end = child_style.margin.end.maybe_resolve(container_width);
1478            let end = child_position_end.maybe_add(child_margin_end);
1479
1480            // Y-axis
1481            let child_position_top = child_style.position.top.maybe_resolve(container_height);
1482            let child_margin_top = child_style.margin.top.maybe_resolve(container_height);
1483            let top = child_position_top.maybe_add(child_margin_top);
1484
1485            let child_position_bottom = child_style.position.bottom.maybe_resolve(container_height);
1486            let child_margin_bottom = child_style.margin.bottom.maybe_resolve(container_height);
1487            let bottom = child_position_bottom.maybe_add(child_margin_bottom);
1488
1489            let (start_main, end_main) = if constants.is_row { (start, end) } else { (top, bottom) };
1490            let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (start, end) };
1491
1492            let mut width = child_style
1493                .size
1494                .width
1495                .maybe_resolve(container_width)
1496                .maybe_max(child_style.min_size.width.maybe_resolve(container_width))
1497                .maybe_min(child_style.max_size.width.maybe_resolve(container_width));
1498
1499            if width.is_none() && start.is_some() && end.is_some() {
1500                width = container_width.maybe_sub(start).maybe_sub(end);
1501            }
1502
1503            let mut height: Option<f32> = child_style
1504                .size
1505                .height
1506                .maybe_resolve(container_height)
1507                .maybe_max(child_style.min_size.height.maybe_resolve(container_height))
1508                .maybe_min(child_style.max_size.height.maybe_resolve(container_height));
1509
1510            if height.is_none() && top.is_some() && bottom.is_some() {
1511                height = container_height.maybe_sub(top).maybe_sub(bottom);
1512            }
1513
1514            let preliminary_size = self.compute_preliminary(
1515                child,
1516                Size { width, height },
1517                Size { width: container_width, height: container_height },
1518                true,
1519                false,
1520            );
1521
1522            let free_main_space = constants.container_size.main(constants.dir)
1523                - preliminary_size
1524                    .main(constants.dir)
1525                    .maybe_max(
1526                        child_style
1527                            .min_main_size(constants.dir)
1528                            .maybe_resolve(constants.node_inner_size.main(constants.dir)),
1529                    )
1530                    .maybe_min(
1531                        child_style
1532                            .max_main_size(constants.dir)
1533                            .maybe_resolve(constants.node_inner_size.main(constants.dir)),
1534                    );
1535
1536            let free_cross_space = constants.container_size.cross(constants.dir)
1537                - preliminary_size
1538                    .cross(constants.dir)
1539                    .maybe_max(
1540                        child_style
1541                            .min_cross_size(constants.dir)
1542                            .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
1543                    )
1544                    .maybe_min(
1545                        child_style
1546                            .max_cross_size(constants.dir)
1547                            .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
1548                    );
1549
1550            let offset_main = if start_main.is_some() {
1551                start_main.unwrap_or(0.0) + constants.border.main_start(constants.dir)
1552            } else if end_main.is_some() {
1553                free_main_space - end_main.unwrap_or(0.0) - constants.border.main_end(constants.dir)
1554            } else {
1555                match self.nodes[node].style.justify_content {
1556                    JustifyContent::SpaceBetween | JustifyContent::FlexStart => {
1557                        constants.padding_border.main_start(constants.dir)
1558                    }
1559                    JustifyContent::FlexEnd => free_main_space - constants.padding_border.main_end(constants.dir),
1560                    JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
1561                        free_main_space / 2.0
1562                    }
1563                }
1564            };
1565
1566            let offset_cross = if start_cross.is_some() {
1567                start_cross.unwrap_or(0.0) + constants.border.cross_start(constants.dir)
1568            } else if end_cross.is_some() {
1569                free_cross_space - end_cross.unwrap_or(0.0) - constants.border.cross_end(constants.dir)
1570            } else {
1571                match child_style.align_self(&self.nodes[node].style) {
1572                    AlignSelf::Auto => unreachable!(),
1573                    AlignSelf::FlexStart => {
1574                        if constants.is_wrap_reverse {
1575                            free_cross_space - constants.padding_border.cross_end(constants.dir)
1576                        } else {
1577                            constants.padding_border.cross_start(constants.dir)
1578                        }
1579                    }
1580                    AlignSelf::FlexEnd => {
1581                        if constants.is_wrap_reverse {
1582                            constants.padding_border.cross_start(constants.dir)
1583                        } else {
1584                            free_cross_space - constants.padding_border.cross_end(constants.dir)
1585                        }
1586                    }
1587                    AlignSelf::Center => free_cross_space / 2.0,
1588                    AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
1589                    AlignSelf::Stretch => {
1590                        if constants.is_wrap_reverse {
1591                            free_cross_space - constants.padding_border.cross_end(constants.dir)
1592                        } else {
1593                            constants.padding_border.cross_start(constants.dir)
1594                        }
1595                    }
1596                }
1597            };
1598
1599            self.nodes[child].layout = Layout {
1600                order: order as u32,
1601                size: preliminary_size,
1602                location: Point {
1603                    x: if constants.is_row { offset_main } else { offset_cross },
1604                    y: if constants.is_column { offset_main } else { offset_cross },
1605                },
1606            };
1607        }
1608    }
1609
1610    /// Compute a preliminary size for an item
1611    fn compute_preliminary(
1612        &mut self,
1613        node: Node,
1614        node_size: Size<Option<f32>>,
1615        parent_size: Size<Option<f32>>,
1616        perform_layout: bool,
1617        main_size: bool,
1618    ) -> Size<f32> {
1619        self.nodes[node].is_dirty = false;
1620
1621        // First we check if we have a result for the given input
1622        if let Some(cached_size) = self.compute_from_cache(node, node_size, parent_size, perform_layout, main_size) {
1623            return cached_size;
1624        }
1625
1626        // Define some general constants we will need for the remainder of the algorithm.
1627        let mut constants = Taffy::compute_constants(&self.nodes[node], node_size, parent_size);
1628
1629        // If this is a leaf node we can skip a lot of this function in some cases
1630        if self.children[node].is_empty() {
1631            if node_size.width.is_some() && node_size.height.is_some() {
1632                return node_size.map(|s| s.unwrap_or(0.0));
1633            }
1634
1635            if let Some(ref measure) = self.nodes[node].measure {
1636                let converted_size = match measure {
1637                    MeasureFunc::Raw(measure) => measure(node_size),
1638
1639                    #[cfg(any(feature = "std", feature = "alloc"))]
1640                    MeasureFunc::Boxed(measure) => measure(node_size),
1641                };
1642                *self.cache(node, main_size) =
1643                    Some(Cache { node_size, parent_size, perform_layout, size: converted_size });
1644                return converted_size;
1645            }
1646
1647            return Size {
1648                width: node_size.width.unwrap_or(0.0) + constants.padding_border.horizontal_axis_sum(),
1649                height: node_size.height.unwrap_or(0.0) + constants.padding_border.vertical_axis_sum(),
1650            };
1651        }
1652
1653        // 9. Flex Layout Algorithm
1654
1655        // 9.1. Initial Setup
1656
1657        // 1. Generate anonymous flex items as described in §4 Flex Items.
1658        let mut flex_items = self.generate_anonymous_flex_items(node, &constants);
1659
1660        // 9.2. Line Length Determination
1661
1662        // 2. Determine the available main and cross space for the flex items.
1663        let available_space = Self::determine_available_space(node_size, parent_size, &constants);
1664
1665        let has_baseline_child = flex_items
1666            .iter()
1667            .any(|child| self.nodes[child.node].style.align_self(&self.nodes[node].style) == AlignSelf::Baseline);
1668
1669        // 3. Determine the flex base size and hypothetical main size of each item.
1670        self.determine_flex_base_size(node, node_size, &constants, available_space, &mut flex_items);
1671
1672        // TODO: Add step 4 according to spec: https://www.w3.org/TR/css-flexbox-1/#algo-main-container
1673        // 9.3. Main Size Determination
1674
1675        // 5. Collect flex items into flex lines.
1676        let mut flex_lines = self.collect_flex_lines(node, &constants, available_space, &mut flex_items);
1677
1678        // 6. Resolve the flexible lengths of all the flex items to find their used main size.
1679        for line in &mut flex_lines {
1680            self.resolve_flexible_lengths(line, &constants, available_space);
1681        }
1682
1683        // TODO: Cleanup and make according to spec
1684        // Not part of the spec from what i can see but seems correct
1685        constants.container_size.set_main(
1686            constants.dir,
1687            node_size.main(constants.dir).unwrap_or({
1688                let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| {
1689                    let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(constants.dir)).sum();
1690                    acc.max(length)
1691                });
1692
1693                let size = longest_line + constants.padding_border.main_axis_sum(constants.dir);
1694                match available_space.main(constants.dir) {
1695                    Some(val) if flex_lines.len() > 1 && size < val => val,
1696                    _ => size,
1697                }
1698            }),
1699        );
1700
1701        constants.inner_container_size.set_main(
1702            constants.dir,
1703            constants.container_size.main(constants.dir) - constants.padding_border.main_axis_sum(constants.dir),
1704        );
1705
1706        // 9.4. Cross Size Determination
1707
1708        // 7. Determine the hypothetical cross size of each item.
1709        for line in &mut flex_lines {
1710            self.determine_hypothetical_cross_size(line, &constants, available_space);
1711        }
1712
1713        // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
1714        // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
1715        if has_baseline_child {
1716            self.calculate_children_base_lines(node, node_size, &mut flex_lines, &constants);
1717        }
1718
1719        // 8. Calculate the cross size of each flex line.
1720        self.calculate_cross_size(&mut flex_lines, node, node_size, &constants);
1721
1722        // 9. Handle 'align-content: stretch'.
1723        self.handle_align_content_stretch(&mut flex_lines, node, node_size, &constants);
1724
1725        // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
1726        //     note the cross size of the line they’re in as the item’s strut size, and restart
1727        //     layout from the beginning.
1728        //
1729        //     In this second layout round, when collecting items into lines, treat the collapsed
1730        //     items as having zero main size. For the rest of the algorithm following that step,
1731        //     ignore the collapsed items entirely (as if they were display:none) except that after
1732        //     calculating the cross size of the lines, if any line’s cross size is less than the
1733        //     largest strut size among all the collapsed items in the line, set its cross size to
1734        //     that strut size.
1735        //
1736        //     Skip this step in the second layout round.
1737
1738        // TODO implement once (if ever) we support visibility:collapse
1739
1740        // 11. Determine the used cross size of each flex item.
1741        self.determine_used_cross_size(&mut flex_lines, node, &constants);
1742
1743        // 9.5. Main-Axis Alignment
1744
1745        // 12. Distribute any remaining free space.
1746        self.distribute_remaining_free_space(&mut flex_lines, node, &constants);
1747
1748        // 9.6. Cross-Axis Alignment
1749
1750        // 13. Resolve cross-axis auto margins (also includes 14).
1751        self.resolve_cross_axis_auto_margins(&mut flex_lines, node, &constants);
1752
1753        // 15. Determine the flex container’s used cross size.
1754        let total_cross_size = Self::determine_container_cross_size(&mut flex_lines, node_size, &mut constants);
1755
1756        // We have the container size.
1757        // If our caller does not care about performing layout we are done now.
1758        if !perform_layout {
1759            let container_size = constants.container_size;
1760            *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1761            return container_size;
1762        }
1763
1764        // 16. Align all flex lines per align-content.
1765        self.align_flex_lines_per_align_content(&mut flex_lines, node, &constants, total_cross_size);
1766
1767        // Do a final layout pass and gather the resulting layouts
1768        self.final_layout_pass(node, &mut flex_lines, &constants);
1769
1770        // Before returning we perform absolute layout on all absolutely positioned children
1771        self.perform_absolute_layout_on_absolute_children(node, &constants);
1772
1773        // Recursively hide all nodes set to Display::None
1774        for (order, child) in self.children[node].iter().enumerate() {
1775            if self.nodes[*child].style.display == Display::None {
1776                hidden_layout(&mut self.nodes, &self.children, *child, order as _);
1777            }
1778        }
1779
1780        let container_size = constants.container_size;
1781        *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1782
1783        container_size
1784    }
1785}
1786
1787/// Creates a zero-sized layout for this node and its children, recursively.
1788fn hidden_layout(
1789    nodes: &mut SlotMap<Node, NodeData>,
1790    children: &SlotMap<Node, ChildrenVec<Node>>,
1791    node: Node,
1792    order: u32,
1793) {
1794    nodes[node].layout = Layout::with_order(order);
1795
1796    for (order, child) in children[node].iter().enumerate() {
1797        hidden_layout(nodes, children, *child, order as _);
1798    }
1799}
1800
1801#[cfg(test)]
1802mod tests {
1803    use crate::flexbox::hidden_layout;
1804    use crate::geometry::Point;
1805    use crate::style::Display;
1806    use crate::style::Display::Flex;
1807    use crate::{
1808        math::MaybeMath,
1809        prelude::{Rect, Size},
1810        resolve::ResolveOrDefault,
1811        style::{FlexWrap, FlexboxLayout},
1812        Taffy,
1813    };
1814
1815    // Make sure we get correct constants
1816    #[test]
1817    fn correct_constants() {
1818        let mut tree = Taffy::with_capacity(16);
1819
1820        let style = FlexboxLayout::default();
1821        let node_id = tree.new_leaf(style).unwrap();
1822
1823        let node_size = Size::NONE;
1824        let parent_size = Size::NONE;
1825
1826        let constants = Taffy::compute_constants(&tree.nodes[node_id], node_size, parent_size);
1827
1828        assert!(constants.dir == style.flex_direction);
1829        assert!(constants.is_row == style.flex_direction.is_row());
1830        assert!(constants.is_column == style.flex_direction.is_column());
1831        assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
1832
1833        let margin = style.margin.resolve_or_default(parent_size);
1834        assert_eq!(constants.margin, margin);
1835
1836        let border = style.border.resolve_or_default(parent_size);
1837        assert_eq!(constants.border, border);
1838
1839        let padding = style.padding.resolve_or_default(parent_size);
1840
1841        // TODO: Replace with something less hardcoded?
1842        let padding_border = Rect {
1843            start: padding.start + border.start,
1844            end: padding.end + border.end,
1845            top: padding.top + border.top,
1846            bottom: padding.bottom + border.bottom,
1847        };
1848
1849        assert_eq!(constants.padding_border, padding_border);
1850
1851        // TODO: Replace with something less hardcoded?
1852        let inner_size = Size {
1853            width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
1854            height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
1855        };
1856        assert_eq!(constants.node_inner_size, inner_size);
1857
1858        assert_eq!(constants.container_size, Size::ZERO);
1859        assert_eq!(constants.inner_container_size, Size::ZERO);
1860    }
1861
1862    #[test]
1863    fn hidden_layout_should_hide_recursively() {
1864        let mut taffy = Taffy::new();
1865
1866        let style: FlexboxLayout =
1867            FlexboxLayout { display: Flex, size: Size::from_points(50.0, 50.0), ..Default::default() };
1868
1869        let grandchild_00 = taffy.new_leaf(style).unwrap();
1870
1871        let grandchild_01 = taffy.new_leaf(style).unwrap();
1872
1873        let grandchild_02 = taffy.new_leaf(style).unwrap();
1874
1875        let child_00 = taffy.new_with_children(style, &[grandchild_00, grandchild_01]).unwrap();
1876
1877        let child_01 = taffy.new_with_children(style, &[grandchild_02]).unwrap();
1878
1879        let root = taffy
1880            .new_with_children(
1881                FlexboxLayout { display: Display::None, size: Size::from_points(50.0, 50.0), ..Default::default() },
1882                &[child_00, child_01],
1883            )
1884            .unwrap();
1885
1886        hidden_layout(&mut taffy.nodes, &taffy.children, root, 0);
1887
1888        // Whatever size and display-mode the nodes had previously,
1889        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
1890        for (node, _) in &taffy.nodes {
1891            if let Ok(layout) = taffy.layout(node) {
1892                assert_eq!(layout.size, Size::ZERO);
1893                assert_eq!(layout.location, Point::ZERO);
1894            }
1895        }
1896    }
1897}