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