Strict types for style values (#269)
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}