1#[cfg(not(feature = "std"))]
2use alloc::{vec, vec::Vec};
3#[cfg(not(feature = "std"))]
4use libm::F32Ext;
5
6use core::f32;
7
8use std::cell::RefCell;
9use std::rc::Rc;
10
11use crate::node::InternalNode;
12use crate::result;
13use crate::result::Result;
14use crate::style::*;
15
16use crate::number::Number::*;
17use crate::number::*;
18
19use crate::geometry::{Point, Rect, Size};
20
21#[derive(Debug, Clone)]
22pub struct ComputeResult {
23 pub size: Size<f32>,
24 pub children: Vec<result::Layout>,
25}
26
27struct FlexItem<'a> {
28 node: &'a Rc<RefCell<InternalNode>>,
29
30 size: Size<Number>,
31 min_size: Size<Number>,
32 max_size: Size<Number>,
33
34 position: Rect<Number>,
35 margin: Rect<f32>,
36 padding: Rect<f32>,
37 border: Rect<f32>,
38
39 flex_basis: f32,
40 inner_flex_basis: f32,
41 violation: f32,
42 frozen: bool,
43
44 hypothetical_inner_size: Size<f32>,
45 hypothetical_outer_size: Size<f32>,
46 target_size: Size<f32>,
47 outer_target_size: Size<f32>,
48
49 baseline: f32,
50
51 // temporary values for holding offset in the main / cross direction.
52 // offset is the relative position from the item's natural flow position based on
53 // relative position values, alignment, and justification. Does not include margin/padding/border.
54 offset_main: f32,
55 offset_cross: f32,
56}
57
58struct FlexLine<'a> {
59 items: Vec<FlexItem<'a>>,
60 cross_size: f32,
61 offset_cross: f32,
62}
63
64pub(crate) fn compute(root: &mut InternalNode, size: Size<Number>) -> Result<result::Layout> {
65 let has_root_min_max = root.style.min_size.width.is_defined()
66 || root.style.min_size.height.is_defined()
67 || root.style.max_size.width.is_defined()
68 || root.style.max_size.height.is_defined();
69
70 let result = if has_root_min_max {
71 let first_pass = compute_internal(
72 root,
73 Size {
74 width: root.style.size.width.resolve(size.width),
75 height: root.style.size.height.resolve(size.height),
76 },
77 size,
78 false,
79 )?;
80
81 compute_internal(
82 root,
83 Size {
84 width: first_pass
85 .size
86 .width
87 .maybe_max(root.style.min_size.width.resolve(size.width))
88 .maybe_min(root.style.max_size.width.resolve(size.width))
89 .to_number(),
90 height: first_pass
91 .size
92 .height
93 .maybe_max(root.style.min_size.height.resolve(size.height))
94 .maybe_min(root.style.max_size.height.resolve(size.height))
95 .to_number(),
96 },
97 size,
98 true,
99 )?
100 } else {
101 compute_internal(
102 root,
103 Size {
104 width: root.style.size.width.resolve(size.width),
105 height: root.style.size.height.resolve(size.height),
106 },
107 size,
108 true,
109 )?
110 };
111
112 let mut layout = result::Layout {
113 order: 0,
114 size: Size { width: result.size.width, height: result.size.height },
115 location: Point { x: 0.0, y: 0.0 },
116 children: result.children,
117 };
118
119 round_layout(&mut layout, 0.0, 0.0);
120 Ok(layout)
121}
122
123fn round_layout(layout: &mut result::Layout, abs_x: f32, abs_y: f32) {
124 let abs_x = abs_x + layout.location.x;
125 let abs_y = abs_y + layout.location.y;
126
127 layout.location.x = layout.location.x.round();
128 layout.location.y = layout.location.y.round();
129 layout.size.width = (abs_x + layout.size.width).round() - abs_x.round();
130 layout.size.height = (abs_y + layout.size.height).round() - abs_y.round();
131 layout.children.iter_mut().for_each(|child| round_layout(child, abs_x, abs_y));
132}
133
134fn compute_internal(
135 node: &mut InternalNode,
136 node_size: Size<Number>,
137 parent_size: Size<Number>,
138 perform_layout: bool,
139) -> Result<ComputeResult> {
140 node.is_dirty = false;
141
142 // First we check if we have a result for the given input
143 if let Some(cache) = &node.layout_cache {
144 if cache.perform_layout || !perform_layout {
145 let width_compatible = if let Number::Defined(width) = node_size.width {
146 (width - cache.result.size.width).abs() < f32::EPSILON
147 } else {
148 cache.node_size.width.is_undefined()
149 };
150
151 let height_compatible = if let Number::Defined(height) = node_size.height {
152 (height - cache.result.size.height).abs() < f32::EPSILON
153 } else {
154 cache.node_size.height.is_undefined()
155 };
156
157 if width_compatible && height_compatible {
158 return Ok(cache.result.clone());
159 }
160
161 if cache.node_size == node_size && cache.parent_size == parent_size {
162 return Ok(cache.result.clone());
163 }
164 }
165 }
166
167 // Define some general constants we will need for the remainder
168 // of the algorithm.
169
170 let dir = node.style.flex_direction;
171 let is_row = dir.is_row();
172 let is_column = dir.is_column();
173 let is_wrap_reverse = node.style.flex_wrap == FlexWrap::WrapReverse;
174
175 let margin = node.style.margin.map(|n| n.resolve(parent_size.width).or_else(0.0));
176 let padding = node.style.padding.map(|n| n.resolve(parent_size.width).or_else(0.0));
177 let border = node.style.border.map(|n| n.resolve(parent_size.width).or_else(0.0));
178
179 let padding_border = Rect {
180 start: padding.start + border.start,
181 end: padding.end + border.end,
182 top: padding.top + border.top,
183 bottom: padding.bottom + border.bottom,
184 };
185
186 let node_inner_size = Size {
187 width: node_size.width - padding_border.horizontal(),
188 height: node_size.height - padding_border.vertical(),
189 };
190
191 let mut container_size = Size { width: 0.0, height: 0.0 };
192 let mut inner_container_size = Size { width: 0.0, height: 0.0 };
193
194 // If this is a leaf node we can skip a lot of this function in some cases
195 if node.children.is_empty() {
196 if node_size.width.is_defined() && node_size.height.is_defined() {
197 return Ok(ComputeResult { size: node_size.map(|s| s.or_else(0.0)), children: vec![] });
198 }
199
200 if let Some(ref measure) = node.measure {
201 let result = ComputeResult { size: measure(node_size)?, children: vec![] };
202 node.layout_cache = Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() });
203 return Ok(result);
204 }
205
206 return Ok(ComputeResult {
207 size: Size {
208 width: node_size.width.or_else(0.0) + padding_border.horizontal(),
209 height: node_size.height.or_else(0.0) + padding_border.vertical(),
210 },
211 children: vec![],
212 });
213 }
214
215 // 9.2. Line Length Determination
216
217 // 1. Generate anonymous flex items as described in §4 Flex Items.
218
219 // 2. Determine the available main and cross space for the flex items.
220 // For each dimension, if that dimension of the flex container’s content box
221 // is a definite size, use that; if that dimension of the flex container is
222 // being sized under a min or max-content constraint, the available space in
223 // that dimension is that constraint; otherwise, subtract the flex container’s
224 // margin, border, and padding from the space available to the flex container
225 // in that dimension and use that value. This might result in an infinite value.
226
227 let available_space = Size {
228 width: node_size.width.or_else(parent_size.width - margin.horizontal()) - padding_border.horizontal(),
229 height: node_size.height.or_else(parent_size.height - margin.vertical()) - padding_border.vertical(),
230 };
231
232 let mut flex_items: Vec<FlexItem> = node
233 .children
234 .iter()
235 .filter(|child| child.borrow().style.position_type != PositionType::Absolute)
236 .filter(|child| child.borrow().style.display != Display::None)
237 .map(|child| FlexItem {
238 node: child,
239
240 size: Size {
241 width: child.borrow().style.size.width.resolve(node_inner_size.width),
242 height: child.borrow().style.size.height.resolve(node_inner_size.height),
243 },
244
245 min_size: Size {
246 width: child.borrow().style.min_size.width.resolve(node_inner_size.width),
247 height: child.borrow().style.min_size.height.resolve(node_inner_size.height),
248 },
249
250 max_size: Size {
251 width: child.borrow().style.max_size.width.resolve(node_inner_size.width),
252 height: child.borrow().style.max_size.height.resolve(node_inner_size.height),
253 },
254
255 position: child.borrow().style.position.map(|p| p.resolve(node_inner_size.width)),
256 margin: child.borrow().style.margin.map(|m| m.resolve(node_inner_size.width).or_else(0.0)),
257 padding: child.borrow().style.padding.map(|p| p.resolve(node_inner_size.width).or_else(0.0)),
258 border: child.borrow().style.border.map(|b| b.resolve(node_inner_size.width).or_else(0.0)),
259
260 flex_basis: 0.0,
261 inner_flex_basis: 0.0,
262 violation: 0.0,
263 frozen: false,
264
265 hypothetical_inner_size: Size { width: 0.0, height: 0.0 },
266 hypothetical_outer_size: Size { width: 0.0, height: 0.0 },
267 target_size: Size { width: 0.0, height: 0.0 },
268 outer_target_size: Size { width: 0.0, height: 0.0 },
269
270 baseline: 0.0,
271
272 offset_main: 0.0,
273 offset_cross: 0.0,
274 })
275 .collect();
276
277 let has_baseline_child = flex_items.iter().fold(false, |result, child| {
278 result || child.node.borrow().style.align_self(&node.style) == AlignSelf::Baseline
279 });
280
281 // TODO - this does not follow spec. See commented out code below
282 // 3. Determine the flex base size and hypothetical main size of each item:
283 flex_items.iter_mut().try_for_each(|child| -> Result<()> {
284 // A. If the item has a definite used flex basis, that’s the flex base size.
285
286 let flex_basis = child.node.borrow().style.flex_basis.resolve(node_inner_size.main(dir));
287 if flex_basis.is_defined() {
288 child.flex_basis = flex_basis.or_else(0.0);
289 return Ok(());
290 };
291
292 // B. If the flex item has an intrinsic aspect ratio,
293 // a used flex basis of content, and a definite cross size,
294 // then the flex base size is calculated from its inner
295 // cross size and the flex item’s intrinsic aspect ratio.
296
297 if let Defined(ratio) = child.node.borrow().style.aspect_ratio {
298 if let Defined(cross) = node_size.cross(dir) {
299 if child.node.borrow().style.flex_basis == Dimension::Auto {
300 child.flex_basis = cross * ratio;
301 return Ok(());
302 }
303 }
304 }
305
306 // C. If the used flex basis is content or depends on its available space,
307 // and the flex container is being sized under a min-content or max-content
308 // constraint (e.g. when performing automatic table layout [CSS21]),
309 // size the item under that constraint. The flex base size is the item’s
310 // resulting main size.
311
312 // TODO - Probably need to cover this case in future
313
314 // D. Otherwise, if the used flex basis is content or depends on its
315 // available space, the available main size is infinite, and the flex item’s
316 // inline axis is parallel to the main axis, lay the item out using the rules
317 // for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
318 // is the item’s max-content main size.
319
320 // TODO - Probably need to cover this case in future
321
322 // E. Otherwise, size the item into the available space using its used flex basis
323 // in place of its main size, treating a value of content as max-content.
324 // If a cross size is needed to determine the main size (e.g. when the
325 // flex item’s main size is in its block axis) and the flex item’s cross size
326 // is auto and not definite, in this calculation use fit-content as the
327 // flex item’s cross size. The flex base size is the item’s resulting main size.
328
329 let width: Number = if !child.size.width.is_defined()
330 && child.node.borrow().style.align_self(&node.style) == AlignSelf::Stretch
331 && is_column
332 {
333 available_space.width
334 } else {
335 child.size.width
336 };
337
338 let height: Number = if !child.size.height.is_defined()
339 && child.node.borrow().style.align_self(&node.style) == AlignSelf::Stretch
340 && is_row
341 {
342 available_space.height
343 } else {
344 child.size.height
345 };
346
347 child.flex_basis = compute_internal(
348 &mut child.node.borrow_mut(),
349 Size {
350 width: width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
351 height: height.maybe_max(child.min_size.height).maybe_min(child.max_size.height),
352 },
353 available_space,
354 false,
355 )?
356 .size
357 .main(dir)
358 .maybe_max(child.min_size.main(dir))
359 .maybe_min(child.max_size.main(dir));
360
361 Ok(())
362 })?;
363
364 // The hypothetical main size is the item’s flex base size clamped according to its
365 // used min and max main sizes (and flooring the content box size at zero).
366
367 flex_items.iter_mut().try_for_each(|child| -> Result<()> {
368 child.inner_flex_basis = child.flex_basis - child.padding.main(dir) - child.border.main(dir);
369
370 // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
371 // The following logic was developed not from the spec but by trail and error looking into how
372 // webkit handled various scenarios. Can probably be solved better by passing in
373 // min-content max-content constraints from the top
374 let min_main = compute_internal(
375 &mut child.node.borrow_mut(),
376 Size { width: Undefined, height: Undefined },
377 available_space,
378 false,
379 )?
380 .size
381 .main(dir)
382 .maybe_max(child.min_size.main(dir))
383 .maybe_min(child.size.main(dir))
384 .to_number();
385
386 child
387 .hypothetical_inner_size
388 .set_main(dir, child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(dir)));
389
390 child.hypothetical_outer_size.set_main(dir, child.hypothetical_inner_size.main(dir) + child.margin.main(dir));
391
392 Ok(())
393 })?;
394
395 // 9.3. Main Size Determination
396
397 // 5. Collect flex items into flex lines:
398 // - If the flex container is single-line, collect all the flex items into
399 // a single flex line.
400 // - Otherwise, starting from the first uncollected item, collect consecutive
401 // items one by one until the first time that the next collected item would
402 // not fit into the flex container’s inner main size (or until a forced break
403 // is encountered, see §10 Fragmenting Flex Layout). If the very first
404 // uncollected item wouldn’t fit, collect just it into the line.
405 //
406 // For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.)
407 // Repeat until all flex items have been collected into flex lines
408 //
409 // Note that the "collect as many" line will collect zero-sized flex items onto
410 // the end of the previous line even if the last non-zero item exactly "filled up" the line.
411
412 let mut flex_lines = {
413 let mut lines: Vec<FlexLine> = vec![];
414 let mut line_length = 0.0;
415
416 if node.style.flex_wrap == FlexWrap::NoWrap {
417 lines.push(FlexLine { items: flex_items, cross_size: 0.0, offset_cross: 0.0 });
418 } else {
419 let mut line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
420
421 for child in flex_items {
422 line_length += child.hypothetical_outer_size.main(dir);
423
424 if let Defined(main) = available_space.main(dir) {
425 if line_length > main && !line.items.is_empty() {
426 line_length = child.hypothetical_outer_size.main(dir);
427 lines.push(line);
428 line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
429 }
430 }
431
432 line.items.push(child);
433 }
434
435 lines.push(line);
436 }
437
438 lines
439 };
440
441 // 6. Resolve the flexible lengths of all the flex items to find their used main size.
442 // See §9.7 Resolving Flexible Lengths.
443 //
444 // 9.7. Resolving Flexible Lengths
445
446 flex_lines.iter_mut().try_for_each(|line| -> Result<()> {
447 // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
448 // items on the line. If the sum is less than the flex container’s inner main size,
449 // use the flex grow factor for the rest of this algorithm; otherwise, use the
450 // flex shrink factor.
451
452 let used_flex_factor: f32 = line.items.iter().map(|child| child.hypothetical_outer_size.main(dir)).sum();
453 let growing = used_flex_factor < node_inner_size.main(dir).or_else(0.0);
454 let shrinking = !growing;
455
456 // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
457 // - Any item that has a flex factor of zero
458 // - If using the flex grow factor: any item that has a flex base size
459 // greater than its hypothetical main size
460 // - If using the flex shrink factor: any item that has a flex base size
461 // smaller than its hypothetical main size
462
463 line.items.iter_mut().try_for_each(|child| -> Result<()> {
464 // TODO - This is not found by reading the spec. Maybe this can be done in some other place
465 // instead. This was found by trail and error fixing tests to align with webkit output.
466 if node_inner_size.main(dir).is_undefined() && is_row {
467 child.target_size.set_main(
468 dir,
469 compute_internal(
470 &mut child.node.borrow_mut(),
471 Size {
472 width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
473 height: child.size.height.maybe_max(child.min_size.height).maybe_min(child.max_size.height),
474 },
475 available_space,
476 false,
477 )?
478 .size
479 .main(dir)
480 .maybe_max(child.min_size.main(dir))
481 .maybe_min(child.max_size.main(dir)),
482 );
483 } else {
484 child.target_size.set_main(dir, child.hypothetical_inner_size.main(dir));
485 }
486
487 // TODO this should really only be set inside the if-statement below but
488 // that causes the target_main_size to never be set for some items
489
490 child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir));
491
492 if (child.node.borrow().style.flex_grow == 0.0 && child.node.borrow().style.flex_shrink == 0.0)
493 || (growing && child.flex_basis > child.hypothetical_inner_size.main(dir))
494 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(dir))
495 {
496 child.frozen = true;
497 }
498
499 Ok(())
500 })?;
501
502 // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
503 // and subtract this from the flex container’s inner main size. For frozen items,
504 // use their outer target main size; for other items, use their outer flex base size.
505
506 let used_space: f32 = line
507 .items
508 .iter()
509 .map(|child| {
510 child.margin.main(dir) + if child.frozen { child.target_size.main(dir) } else { child.flex_basis }
511 })
512 .sum();
513
514 let initial_free_space = (node_inner_size.main(dir) - used_space).or_else(0.0);
515
516 // 4. Loop
517
518 loop {
519 // a. Check for flexible items. If all the flex items on the line are frozen,
520 // free space has been distributed; exit this loop.
521
522 let mut frozen: Vec<&mut FlexItem> = vec![];
523 let mut unfrozen: Vec<&mut FlexItem> = vec![];
524
525 line.items.iter_mut().for_each(|child| {
526 if child.frozen {
527 frozen.push(child);
528 } else {
529 unfrozen.push(child);
530 }
531 });
532
533 if unfrozen.is_empty() {
534 break;
535 }
536
537 // b. Calculate the remaining free space as for initial free space, above.
538 // If the sum of the unfrozen flex items’ flex factors is less than one,
539 // multiply the initial free space by this sum. If the magnitude of this
540 // value is less than the magnitude of the remaining free space, use this
541 // as the remaining free space.
542
543 let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter())
544 .map(|child| {
545 child.margin.main(dir) + if child.frozen { child.target_size.main(dir) } else { child.flex_basis }
546 })
547 .sum();
548
549 let sum_flex_grow: f32 = unfrozen.iter().map(|item| item.node.borrow().style.flex_grow).sum();
550 let sum_flex_shrink: f32 = unfrozen.iter().map(|item| item.node.borrow().style.flex_shrink).sum();
551
552 let free_space = if growing && sum_flex_grow < 1.0 {
553 (initial_free_space * sum_flex_grow).maybe_min(node_inner_size.main(dir) - used_space)
554 } else if shrinking && sum_flex_shrink < 1.0 {
555 (initial_free_space * sum_flex_shrink).maybe_max(node_inner_size.main(dir) - used_space)
556 } else {
557 (node_inner_size.main(dir) - used_space).or_else(0.0)
558 };
559
560 // c. Distribute free space proportional to the flex factors.
561 // - If the remaining free space is zero
562 // Do Nothing
563 // - If using the flex grow factor
564 // Find the ratio of the item’s flex grow factor to the sum of the
565 // flex grow factors of all unfrozen items on the line. Set the item’s
566 // target main size to its flex base size plus a fraction of the remaining
567 // free space proportional to the ratio.
568 // - If using the flex shrink factor
569 // For every unfrozen item on the line, multiply its flex shrink factor by
570 // its inner flex base size, and note this as its scaled flex shrink factor.
571 // Find the ratio of the item’s scaled flex shrink factor to the sum of the
572 // scaled flex shrink factors of all unfrozen items on the line. Set the item’s
573 // target main size to its flex base size minus a fraction of the absolute value
574 // of the remaining free space proportional to the ratio. Note this may result
575 // in a negative inner main size; it will be corrected in the next step.
576 // - Otherwise
577 // Do Nothing
578
579 if free_space.is_normal() {
580 if growing && sum_flex_grow > 0.0 {
581 unfrozen.iter_mut().for_each(|child| {
582 child.target_size.set_main(
583 dir,
584 child.flex_basis + free_space * (child.node.borrow().style.flex_grow / sum_flex_grow),
585 );
586 });
587 } else if shrinking && sum_flex_shrink > 0.0 {
588 let sum_scaled_shrink_factor: f32 = unfrozen
589 .iter()
590 .map(|child| child.inner_flex_basis * child.node.borrow().style.flex_shrink)
591 .sum();
592
593 if sum_scaled_shrink_factor > 0.0 {
594 unfrozen.iter_mut().for_each(|child| {
595 let scaled_shrink_factor = child.inner_flex_basis * child.node.borrow().style.flex_shrink;
596 child.target_size.set_main(
597 dir,
598 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
599 )
600 });
601 }
602 }
603 }
604
605 // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
606 // used min and max main sizes and floor its content-box size at zero. If the
607 // item’s target main size was made smaller by this, it’s a max violation.
608 // If the item’s target main size was made larger by this, it’s a min violation.
609
610 let total_violation = unfrozen.iter_mut().try_fold(0.0, |acc, child| -> Result<f32> {
611 // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
612 // The following logic was developed not from the spec but by trail and error looking into how
613 // webkit handled various scenarios. Can probably be solved better by passing in
614 // min-content max-content constraints from the top. Need to figure out correct thing to do here as
615 // just piling on more conditionals.
616 let min_main = if is_row && child.node.borrow().measure.is_none() {
617 compute_internal(
618 &mut child.node.borrow_mut(),
619 Size { width: Undefined, height: Undefined },
620 available_space,
621 false,
622 )?
623 .size
624 .width
625 .maybe_min(child.size.width)
626 .maybe_max(child.min_size.width)
627 .to_number()
628 } else {
629 child.min_size.main(dir)
630 };
631
632 let max_main = child.max_size.main(dir);
633 let clamped = child.target_size.main(dir).maybe_min(max_main).maybe_max(min_main).max(0.0);
634 child.violation = clamped - child.target_size.main(dir);
635 child.target_size.set_main(dir, clamped);
636 child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir));
637
638 Ok(acc + child.violation)
639 })?;
640
641 // e. Freeze over-flexed items. The total violation is the sum of the adjustments
642 // from the previous step ∑(clamped size - unclamped size). If the total violation is:
643 // - Zero
644 // Freeze all items.
645 // - Positive
646 // Freeze all the items with min violations.
647 // - Negative
648 // Freeze all the items with max violations.
649
650 unfrozen.iter_mut().for_each(|child| match total_violation {
651 v if v > 0.0 => child.frozen = child.violation > 0.0,
652 v if v < 0.0 => child.frozen = child.violation < 0.0,
653 _ => child.frozen = true,
654 })
655
656 // f. Return to the start of this loop.
657 }
658
659 Ok(())
660 })?;
661
662 // Not part of the spec from what i can see but seems correct
663 container_size.set_main(
664 dir,
665 node_size.main(dir).or_else({
666 let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| {
667 let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(dir)).sum();
668 acc.max(length)
669 });
670
671 let size = longest_line + padding_border.main(dir);
672 match available_space.main(dir) {
673 Defined(val) if flex_lines.len() > 1 && size < val => val,
674 _ => size,
675 }
676 }),
677 );
678
679 inner_container_size.set_main(dir, container_size.main(dir) - padding_border.main(dir));
680
681 // 9.4. Cross Size Determination
682
683 // 7. Determine the hypothetical cross size of each item by performing layout with the
684 // used main size and the available space, treating auto as fit-content.
685
686 flex_lines.iter_mut().try_for_each(|line| {
687 line.items.iter_mut().try_for_each(|child| -> Result<()> {
688 let child_cross =
689 child.size.cross(dir).maybe_max(child.min_size.cross(dir)).maybe_min(child.max_size.cross(dir));
690
691 child.hypothetical_inner_size.set_cross(
692 dir,
693 compute_internal(
694 &mut child.node.borrow_mut(),
695 Size {
696 width: if is_row { child.target_size.width.to_number() } else { child_cross },
697 height: if is_row { child_cross } else { child.target_size.height.to_number() },
698 },
699 Size {
700 width: if is_row { container_size.main(dir).to_number() } else { available_space.width },
701 height: if is_row { available_space.height } else { container_size.main(dir).to_number() },
702 },
703 false,
704 )?
705 .size
706 .cross(dir)
707 .maybe_max(child.min_size.cross(dir))
708 .maybe_min(child.max_size.cross(dir)),
709 );
710
711 child
712 .hypothetical_outer_size
713 .set_cross(dir, child.hypothetical_inner_size.cross(dir) + child.margin.cross(dir));
714
715 Ok(())
716 })
717 })?;
718
719 // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
720 // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
721
722 fn calc_baseline(layout: &result::Layout) -> f32 {
723 if layout.children.is_empty() {
724 layout.size.height
725 } else {
726 calc_baseline(&layout.children[0])
727 }
728 };
729
730 if has_baseline_child {
731 flex_lines.iter_mut().try_for_each(|line| {
732 line.items.iter_mut().try_for_each(|child| -> Result<()> {
733 let result = compute_internal(
734 &mut child.node.borrow_mut(),
735 Size {
736 width: if is_row {
737 child.target_size.width.to_number()
738 } else {
739 child.hypothetical_inner_size.width.to_number()
740 },
741 height: if is_row {
742 child.hypothetical_inner_size.height.to_number()
743 } else {
744 child.target_size.height.to_number()
745 },
746 },
747 Size {
748 width: if is_row { container_size.width.to_number() } else { node_size.width },
749 height: if is_row { node_size.height } else { container_size.height.to_number() },
750 },
751 true,
752 )?;
753
754 child.baseline = calc_baseline(&result::Layout {
755 order: node.children.iter().position(|n| Rc::ptr_eq(n, child.node)).unwrap() as u32,
756 size: result.size,
757 location: Point { x: 0.0, y: 0.0 },
758 children: result.children,
759 });
760
761 Ok(())
762 })
763 })?;
764 }
765
766 // 8. Calculate the cross size of each flex line.
767 // If the flex container is single-line and has a definite cross size, the cross size
768 // of the flex line is the flex container’s inner cross size. Otherwise, for each flex line:
769 //
770 // If the flex container is single-line, then clamp the line’s cross-size to be within
771 // the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition
772 // of min/max-width/height applied more generally, this behavior would fall out automatically.
773
774 if flex_lines.len() == 1 && node_size.cross(dir).is_defined() {
775 flex_lines[0].cross_size = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0);
776 } else {
777 flex_lines.iter_mut().for_each(|line| {
778 // 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
779 // align-self is baseline, and whose cross-axis margins are both non-auto. Find the
780 // largest of the distances between each item’s baseline and its hypothetical outer
781 // cross-start edge, and the largest of the distances between each item’s baseline
782 // and its hypothetical outer cross-end edge, and sum these two values.
783
784 // 2. Among all the items not collected by the previous step, find the largest
785 // outer hypothetical cross size.
786
787 // 3. The used cross-size of the flex line is the largest of the numbers found in the
788 // previous two steps and zero.
789
790 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
791 line.cross_size = line
792 .items
793 .iter()
794 .map(|child| {
795 if child.node.borrow().style.align_self(&node.style) == AlignSelf::Baseline
796 && child.node.borrow().style.cross_margin_start(dir) != Dimension::Auto
797 && child.node.borrow().style.cross_margin_end(dir) != Dimension::Auto
798 && child.node.borrow().style.cross_size(dir) == Dimension::Auto
799 {
800 max_baseline - child.baseline + child.hypothetical_outer_size.cross(dir)
801 } else {
802 child.hypothetical_outer_size.cross(dir)
803 }
804 })
805 .fold(0.0, |acc, x| acc.max(x));
806 });
807 }
808
809 // 9. Handle 'align-content: stretch'. If the flex container has a definite cross size,
810 // align-content is stretch, and the sum of the flex lines' cross sizes is less than
811 // the flex container’s inner cross size, increase the cross size of each flex line
812 // by equal amounts such that the sum of their cross sizes exactly equals the
813 // flex container’s inner cross size.
814
815 if node.style.align_content == AlignContent::Stretch && node_size.cross(dir).is_defined() {
816 let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
817 let inner_cross = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0);
818
819 if total_cross < inner_cross {
820 let remaining = inner_cross - total_cross;
821 let addition = remaining / flex_lines.len() as f32;
822 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
823 }
824 }
825
826 // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
827 // note the cross size of the line they’re in as the item’s strut size, and restart
828 // layout from the beginning.
829 //
830 // In this second layout round, when collecting items into lines, treat the collapsed
831 // items as having zero main size. For the rest of the algorithm following that step,
832 // ignore the collapsed items entirely (as if they were display:none) except that after
833 // calculating the cross size of the lines, if any line’s cross size is less than the
834 // largest strut size among all the collapsed items in the line, set its cross size to
835 // that strut size.
836 //
837 // Skip this step in the second layout round.
838
839 // TODO implement once (if ever) we support visibility:collapse
840
841 // 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch,
842 // its computed cross size property is auto, and neither of its cross-axis margins are auto,
843 // the used outer cross size is the used cross size of its flex line, clamped according to
844 // the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s
845 // hypothetical cross size.
846 //
847 // If the flex item has align-self: stretch, redo layout for its contents, treating this
848 // used size as its definite cross size so that percentage-sized children can be resolved.
849 //
850 // Note that this step does not affect the main size of the flex item, even if it has an
851 // intrinsic aspect ratio.
852
853 flex_lines.iter_mut().for_each(|line| {
854 let line_cross_size = line.cross_size;
855
856 line.items.iter_mut().for_each(|child| {
857 child.target_size.set_cross(
858 dir,
859 if child.node.borrow().style.align_self(&node.style) == AlignSelf::Stretch
860 && child.node.borrow().style.cross_margin_start(dir) != Dimension::Auto
861 && child.node.borrow().style.cross_margin_end(dir) != Dimension::Auto
862 && child.node.borrow().style.cross_size(dir) == Dimension::Auto
863 {
864 (line_cross_size - child.margin.cross(dir))
865 .maybe_max(child.min_size.cross(dir))
866 .maybe_min(child.max_size.cross(dir))
867 } else {
868 child.hypothetical_inner_size.cross(dir)
869 },
870 );
871
872 child.outer_target_size.set_cross(dir, child.target_size.cross(dir) + child.margin.cross(dir));
873 });
874 });
875
876 // 9.5. Main-Axis Alignment
877
878 // 12. Distribute any remaining free space. For each flex line:
879 // 1. If the remaining free space is positive and at least one main-axis margin on this
880 // line is auto, distribute the free space equally among these margins. Otherwise,
881 // set all auto margins to zero.
882 // 2. Align the items along the main-axis per justify-content.
883
884 flex_lines.iter_mut().for_each(|line| {
885 let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(dir)).sum();
886 let free_space = inner_container_size.main(dir) - used_space;
887 let mut num_auto_margins = 0;
888
889 line.items.iter_mut().for_each(|child| {
890 if child.node.borrow().style.main_margin_start(dir) == Dimension::Auto {
891 num_auto_margins += 1;
892 }
893 if child.node.borrow().style.main_margin_end(dir) == Dimension::Auto {
894 num_auto_margins += 1;
895 }
896 });
897
898 if free_space > 0.0 && num_auto_margins > 0 {
899 let margin = free_space / num_auto_margins as f32;
900
901 line.items.iter_mut().for_each(|child| {
902 if child.node.borrow().style.main_margin_start(dir) == Dimension::Auto {
903 if is_row {
904 child.margin.start = margin;
905 } else {
906 child.margin.top = margin;
907 }
908 }
909 if child.node.borrow().style.main_margin_end(dir) == Dimension::Auto {
910 if is_row {
911 child.margin.end = margin;
912 } else {
913 child.margin.bottom = margin;
914 }
915 }
916 });
917 } else {
918 let num_items = line.items.len();
919 let layout_reverse = dir.is_reverse();
920
921 let justify_item = |(i, child): (usize, &mut FlexItem)| {
922 let is_first = i == 0;
923
924 child.offset_main = match node.style.justify_content {
925 JustifyContent::FlexStart => {
926 if layout_reverse && is_first {
927 free_space
928 } else {
929 0.0
930 }
931 }
932 JustifyContent::Center => {
933 if is_first {
934 free_space / 2.0
935 } else {
936 0.0
937 }
938 }
939 JustifyContent::FlexEnd => {
940 if is_first && !layout_reverse {
941 free_space
942 } else {
943 0.0
944 }
945 }
946 JustifyContent::SpaceBetween => {
947 if is_first {
948 0.0
949 } else {
950 free_space / (num_items - 1) as f32
951 }
952 }
953 JustifyContent::SpaceAround => {
954 if is_first {
955 (free_space / num_items as f32) / 2.0
956 } else {
957 free_space / num_items as f32
958 }
959 }
960 JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
961 };
962 };
963
964 if layout_reverse {
965 line.items.iter_mut().rev().enumerate().for_each(justify_item);
966 } else {
967 line.items.iter_mut().enumerate().for_each(justify_item);
968 }
969 }
970 });
971
972 // 9.6. Cross-Axis Alignment
973
974 // 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins:
975 // - If its outer cross size (treating those auto margins as zero) is less than the
976 // cross size of its flex line, distribute the difference in those sizes equally
977 // to the auto margins.
978 // - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis)
979 // is auto, set it to zero. Set the opposite margin so that the outer cross size of the
980 // item equals the cross size of its flex line.
981
982 flex_lines.iter_mut().for_each(|line| {
983 let line_cross_size = line.cross_size;
984 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
985
986 line.items.iter_mut().for_each(|child| {
987 let free_space = line_cross_size - child.outer_target_size.cross(dir);
988
989 if child.node.borrow().style.cross_margin_start(dir) == Dimension::Auto
990 && child.node.borrow().style.cross_margin_end(dir) == Dimension::Auto
991 {
992 if is_row {
993 child.margin.top = free_space / 2.0;
994 child.margin.bottom = free_space / 2.0;
995 } else {
996 child.margin.start = free_space / 2.0;
997 child.margin.end = free_space / 2.0;
998 }
999 } else if child.node.borrow().style.cross_margin_start(dir) == Dimension::Auto {
1000 if is_row {
1001 child.margin.top = free_space;
1002 } else {
1003 child.margin.start = free_space;
1004 }
1005 } else if child.node.borrow().style.cross_margin_end(dir) == Dimension::Auto {
1006 if is_row {
1007 child.margin.bottom = free_space;
1008 } else {
1009 child.margin.end = free_space;
1010 }
1011 } else {
1012 // 14. Align all flex items along the cross-axis per align-self, if neither of the item’s
1013 // cross-axis margins are auto.
1014
1015 child.offset_cross = match child.node.borrow().style.align_self(&node.style) {
1016 AlignSelf::Auto => 0.0, // Should never happen
1017 AlignSelf::FlexStart => {
1018 if is_wrap_reverse {
1019 free_space
1020 } else {
1021 0.0
1022 }
1023 }
1024 AlignSelf::FlexEnd => {
1025 if is_wrap_reverse {
1026 0.0
1027 } else {
1028 free_space
1029 }
1030 }
1031 AlignSelf::Center => free_space / 2.0,
1032 AlignSelf::Baseline => {
1033 if is_row {
1034 max_baseline - child.baseline
1035 } else {
1036 // baseline alignment only makes sense if the direction is row
1037 // we treat it as flex-start alignment in columns.
1038 if is_wrap_reverse {
1039 free_space
1040 } else {
1041 0.0
1042 }
1043 }
1044 }
1045 AlignSelf::Stretch => {
1046 if is_wrap_reverse {
1047 free_space
1048 } else {
1049 0.0
1050 }
1051 }
1052 };
1053 }
1054 });
1055 });
1056
1057 // 15. Determine the flex container’s used cross size:
1058 // - If the cross size property is a definite size, use that, clamped by the used
1059 // min and max cross sizes of the flex container.
1060 // - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used
1061 // min and max cross sizes of the flex container.
1062
1063 let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
1064 container_size.set_cross(dir, node_size.cross(dir).or_else(total_cross_size + padding_border.cross(dir)));
1065 inner_container_size.set_cross(dir, container_size.cross(dir) - padding_border.cross(dir));
1066
1067 // We have the container size. If our caller does not care about performing
1068 // layout we are done now.
1069 if !perform_layout {
1070 let result = ComputeResult { size: container_size, children: vec![] };
1071 node.layout_cache = Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() });
1072 return Ok(result);
1073 }
1074
1075 // 16. Align all flex lines per align-content.
1076
1077 let free_space = inner_container_size.cross(dir) - total_cross_size;
1078 let num_lines = flex_lines.len();
1079
1080 let align_line = |(i, line): (usize, &mut FlexLine)| {
1081 let is_first = i == 0;
1082
1083 line.offset_cross = match node.style.align_content {
1084 AlignContent::FlexStart => {
1085 if is_first && is_wrap_reverse {
1086 free_space
1087 } else {
1088 0.0
1089 }
1090 }
1091 AlignContent::FlexEnd => {
1092 if is_first && !is_wrap_reverse {
1093 free_space
1094 } else {
1095 0.0
1096 }
1097 }
1098 AlignContent::Center => {
1099 if is_first {
1100 free_space / 2.0
1101 } else {
1102 0.0
1103 }
1104 }
1105 AlignContent::Stretch => 0.0,
1106 AlignContent::SpaceBetween => {
1107 if is_first {
1108 0.0
1109 } else {
1110 free_space / (num_lines - 1) as f32
1111 }
1112 }
1113 AlignContent::SpaceAround => {
1114 if is_first {
1115 (free_space / num_lines as f32) / 2.0
1116 } else {
1117 free_space / num_lines as f32
1118 }
1119 }
1120 };
1121 };
1122
1123 if is_wrap_reverse {
1124 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1125 } else {
1126 flex_lines.iter_mut().enumerate().for_each(align_line);
1127 }
1128
1129 // Do a final layout pass and gather the resulting layouts
1130 let mut children: Vec<result::Layout> = {
1131 let mut lines: Vec<Vec<result::Layout>> = vec![];
1132 let mut total_offset_cross = padding_border.cross_start(dir);
1133
1134 let layout_line = |line: &mut FlexLine| -> Result<()> {
1135 let mut children: Vec<result::Layout> = vec![];
1136 let mut total_offset_main = padding_border.main_start(dir);
1137 let line_offset_cross = line.offset_cross;
1138
1139 let layout_item = |child: &mut FlexItem| -> Result<()> {
1140 let result = compute_internal(
1141 &mut child.node.borrow_mut(),
1142 child.target_size.map(|s| s.to_number()),
1143 container_size.map(|s| s.to_number()),
1144 true,
1145 )?;
1146
1147 let offset_main = total_offset_main
1148 + child.offset_main
1149 + child.margin.main_start(dir)
1150 + (child.position.main_start(dir).or_else(0.0) - child.position.main_end(dir).or_else(0.0));
1151
1152 let offset_cross = total_offset_cross
1153 + child.offset_cross
1154 + line_offset_cross
1155 + child.margin.cross_start(dir)
1156 + (child.position.cross_start(dir).or_else(0.0) - child.position.cross_end(dir).or_else(0.0));
1157
1158 children.push(result::Layout {
1159 order: node.children.iter().position(|n| Rc::ptr_eq(n, child.node)).unwrap() as u32,
1160 size: result.size,
1161 location: Point {
1162 x: if is_row { offset_main } else { offset_cross },
1163 y: if is_column { offset_main } else { offset_cross },
1164 },
1165 children: result.children,
1166 });
1167
1168 total_offset_main += child.offset_main + child.margin.main(dir) + result.size.main(dir);
1169
1170 Ok(())
1171 };
1172
1173 if dir.is_reverse() {
1174 line.items.iter_mut().rev().try_for_each(layout_item)?;
1175 } else {
1176 line.items.iter_mut().try_for_each(layout_item)?;
1177 }
1178
1179 total_offset_cross += line_offset_cross + line.cross_size;
1180
1181 if dir.is_reverse() {
1182 children.reverse();
1183 }
1184
1185 lines.push(children);
1186
1187 Ok(())
1188 };
1189
1190 if is_wrap_reverse {
1191 flex_lines.iter_mut().rev().try_for_each(layout_line)?;
1192 } else {
1193 flex_lines.iter_mut().try_for_each(layout_line)?;
1194 }
1195
1196 if is_wrap_reverse {
1197 lines.into_iter().rev().flat_map(|x| x).collect()
1198 } else {
1199 lines.into_iter().flat_map(|x| x).collect()
1200 }
1201 };
1202
1203 // Before returning we perform absolute layout on all absolutely positioned children
1204 let absolute_children: Vec<Result<result::Layout>> = node
1205 .children
1206 .iter()
1207 .enumerate()
1208 .filter(|(_, child)| child.borrow().style.position_type == PositionType::Absolute)
1209 .map(|(order, child)| {
1210 let container_width = container_size.width.to_number();
1211 let container_height = container_size.height.to_number();
1212
1213 let start = child.borrow().style.position.start.resolve(container_width)
1214 + child.borrow().style.margin.start.resolve(container_width);
1215 let end = child.borrow().style.position.end.resolve(container_width)
1216 + child.borrow().style.margin.end.resolve(container_width);
1217 let top = child.borrow().style.position.top.resolve(container_height)
1218 + child.borrow().style.margin.top.resolve(container_height);
1219 let bottom = child.borrow().style.position.bottom.resolve(container_height)
1220 + child.borrow().style.margin.bottom.resolve(container_height);
1221
1222 let (start_main, end_main) = if is_row { (start, end) } else { (top, bottom) };
1223 let (start_cross, end_cross) = if is_row { (top, bottom) } else { (start, end) };
1224
1225 let width = child
1226 .borrow()
1227 .style
1228 .size
1229 .width
1230 .resolve(container_width)
1231 .maybe_max(child.borrow().style.min_size.width.resolve(container_width))
1232 .maybe_min(child.borrow().style.max_size.width.resolve(container_width))
1233 .or_else(if start.is_defined() && end.is_defined() {
1234 container_width - start - end
1235 } else {
1236 Undefined
1237 });
1238
1239 let height = child
1240 .borrow()
1241 .style
1242 .size
1243 .height
1244 .resolve(container_height)
1245 .maybe_max(child.borrow().style.min_size.height.resolve(container_height))
1246 .maybe_min(child.borrow().style.max_size.height.resolve(container_height))
1247 .or_else(if top.is_defined() && bottom.is_defined() {
1248 container_height - top - bottom
1249 } else {
1250 Undefined
1251 });
1252
1253 let result = compute_internal(
1254 &mut child.borrow_mut(),
1255 Size { width, height },
1256 Size { width: container_width, height: container_height },
1257 true,
1258 )?;
1259
1260 let free_main_space = container_size.main(dir)
1261 - result
1262 .size
1263 .main(dir)
1264 .maybe_max(child.borrow().style.min_main_size(dir).resolve(node_inner_size.main(dir)))
1265 .maybe_min(child.borrow().style.max_main_size(dir).resolve(node_inner_size.main(dir)));
1266
1267 let free_cross_space = container_size.cross(dir)
1268 - result
1269 .size
1270 .cross(dir)
1271 .maybe_max(child.borrow().style.min_cross_size(dir).resolve(node_inner_size.cross(dir)))
1272 .maybe_min(child.borrow().style.max_cross_size(dir).resolve(node_inner_size.cross(dir)));
1273
1274 let offset_main = if start_main.is_defined() {
1275 start_main.or_else(0.0) + border.main_start(dir)
1276 } else if end_main.is_defined() {
1277 free_main_space - end_main.or_else(0.0) - border.main_end(dir)
1278 } else {
1279 match node.style.justify_content {
1280 JustifyContent::SpaceBetween | JustifyContent::FlexStart => padding_border.main_start(dir),
1281 JustifyContent::FlexEnd => free_main_space - padding_border.main_end(dir),
1282 JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
1283 free_main_space / 2.0
1284 }
1285 }
1286 };
1287
1288 let offset_cross = if start_cross.is_defined() {
1289 start_cross.or_else(0.0) + border.cross_start(dir)
1290 } else if end_cross.is_defined() {
1291 free_cross_space - end_cross.or_else(0.0) - border.cross_end(dir)
1292 } else {
1293 match child.borrow().style.align_self(&node.style) {
1294 AlignSelf::Auto => 0.0, // Should never happen
1295 AlignSelf::FlexStart => {
1296 if is_wrap_reverse {
1297 free_cross_space - padding_border.cross_end(dir)
1298 } else {
1299 padding_border.cross_start(dir)
1300 }
1301 }
1302 AlignSelf::FlexEnd => {
1303 if is_wrap_reverse {
1304 padding_border.cross_start(dir)
1305 } else {
1306 free_cross_space - padding_border.cross_end(dir)
1307 }
1308 }
1309 AlignSelf::Center => free_cross_space / 2.0,
1310 AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
1311 AlignSelf::Stretch => {
1312 if is_wrap_reverse {
1313 free_cross_space - padding_border.cross_end(dir)
1314 } else {
1315 padding_border.cross_start(dir)
1316 }
1317 }
1318 }
1319 };
1320
1321 Ok(result::Layout {
1322 order: order as u32,
1323 size: result.size,
1324 location: Point {
1325 x: if is_row { offset_main } else { offset_cross },
1326 y: if is_column { offset_main } else { offset_cross },
1327 },
1328 children: result.children,
1329 })
1330 })
1331 .collect();
1332
1333 for abs_child in absolute_children {
1334 children.push(abs_child?);
1335 }
1336
1337 fn hidden_layout(node: &InternalNode, order: u32) -> result::Layout {
1338 result::Layout {
1339 order,
1340 size: Size { width: 0.0, height: 0.0 },
1341 location: Point { x: 0.0, y: 0.0 },
1342 children: node
1343 .children
1344 .iter()
1345 .enumerate()
1346 .map(|(order, child)| hidden_layout(&child.borrow(), order as _))
1347 .collect(),
1348 }
1349 }
1350
1351 let mut hidden_children: Vec<result::Layout> = node
1352 .children
1353 .iter()
1354 .enumerate()
1355 .filter(|(_, child)| child.borrow().style.display == Display::None)
1356 .map(|(order, child)| hidden_layout(&child.borrow(), order as _))
1357 .collect();
1358
1359 children.append(&mut hidden_children);
1360 children.sort_by(|c1, c2| c1.order.cmp(&c2.order));
1361
1362 let result = ComputeResult { size: container_size, children };
1363 node.layout_cache = Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() });
1364 Ok(result)
1365}