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