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