gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 500HTML 254C 19JavaScript 81398 Definitions

⎇ d30d23f0a61c39e3412f1354c7981364a38b5e99 src/compute/flexbox.rs Rust · 1943 lines · 79.1 KB History Who touched what raw

Strict types for style values (#269) 0d8c198a9 · Nico Burns ·

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