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