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