1use crate::compute::common::alignment::compute_alignment_offset;
3use crate::geometry::{Line, Point, Rect, Size};
4use crate::style::{
5 AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, FlexWrap, JustifyContent, LengthPercentageAuto,
6 Overflow, Position,
7};
8use crate::style::{CoreStyle, FlexDirection, FlexboxContainerStyle, FlexboxItemStyle};
9use crate::style_helpers::{TaffyMaxContent, TaffyMinContent};
10use crate::tree::{Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
11use crate::tree::{LayoutFlexboxContainer, LayoutPartialTreeExt, NodeId};
12use crate::util::debug::debug_log;
13use crate::util::sys::{f32_max, new_vec_with_capacity, Vec};
14use crate::util::MaybeMath;
15use crate::util::{MaybeResolve, ResolveOrZero};
16use crate::{BoxGenerationMode, BoxSizing};
17
18use super::common::alignment::apply_alignment_fallback;
19#[cfg(feature = "content_size")]
20use super::common::content_size::compute_content_size_contribution;
21
22struct FlexItem {
24 node: NodeId,
26
27 order: u32,
29
30 size: Size<Option<f32>>,
32 min_size: Size<Option<f32>>,
34 max_size: Size<Option<f32>>,
36 align_self: AlignSelf,
38
39 overflow: Point<Overflow>,
41 scrollbar_width: f32,
43 flex_shrink: f32,
45 flex_grow: f32,
47
48 resolved_minimum_main_size: f32,
51
52 inset: Rect<Option<f32>>,
54 margin: Rect<f32>,
56 margin_is_auto: Rect<bool>,
58 padding: Rect<f32>,
60 border: Rect<f32>,
62
63 flex_basis: f32,
65 inner_flex_basis: f32,
67 violation: f32,
69 frozen: bool,
71
72 content_flex_fraction: f32,
75
76 hypothetical_inner_size: Size<f32>,
78 hypothetical_outer_size: Size<f32>,
80 target_size: Size<f32>,
82 outer_target_size: Size<f32>,
84
85 baseline: f32,
87
88 offset_main: f32,
93 offset_cross: f32,
98}
99
100impl FlexItem {
101 fn is_scroll_container(&self) -> bool {
103 self.overflow.x.is_scroll_container() | self.overflow.y.is_scroll_container()
104 }
105}
106
107struct FlexLine<'a> {
109 items: &'a mut [FlexItem],
111 cross_size: f32,
113 offset_cross: f32,
115}
116
117struct AlgoConstants {
119 dir: FlexDirection,
121 is_row: bool,
123 is_column: bool,
125 is_wrap: bool,
127 is_wrap_reverse: bool,
129
130 min_size: Size<Option<f32>>,
132 max_size: Size<Option<f32>>,
134 margin: Rect<f32>,
136 border: Rect<f32>,
138 content_box_inset: Rect<f32>,
141 scrollbar_gutter: Point<f32>,
143 gap: Size<f32>,
145 align_items: AlignItems,
147 align_content: AlignContent,
149 justify_content: Option<JustifyContent>,
151
152 node_outer_size: Size<Option<f32>>,
154 node_inner_size: Size<Option<f32>>,
156
157 container_size: Size<f32>,
159 inner_container_size: Size<f32>,
161}
162
163pub fn compute_flexbox_layout(
165 tree: &mut impl LayoutFlexboxContainer,
166 node: NodeId,
167 inputs: LayoutInput,
168) -> LayoutOutput {
169 let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
170 let style = tree.get_flexbox_container_style(node);
171
172 let aspect_ratio = style.aspect_ratio();
174 let padding = style.padding().resolve_or_zero(parent_size.width);
175 let border = style.border().resolve_or_zero(parent_size.width);
176 let padding_border_sum = padding.sum_axes() + border.sum_axes();
177 let box_sizing_adjustment =
178 if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
179
180 let min_size = style
181 .min_size()
182 .maybe_resolve(parent_size)
183 .maybe_apply_aspect_ratio(aspect_ratio)
184 .maybe_add(box_sizing_adjustment);
185 let max_size = style
186 .max_size()
187 .maybe_resolve(parent_size)
188 .maybe_apply_aspect_ratio(aspect_ratio)
189 .maybe_add(box_sizing_adjustment);
190 let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
191 style
192 .size()
193 .maybe_resolve(parent_size)
194 .maybe_apply_aspect_ratio(aspect_ratio)
195 .maybe_add(box_sizing_adjustment)
196 .maybe_clamp(min_size, max_size)
197 } else {
198 Size::NONE
199 };
200
201 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
203 (Some(min), Some(max)) if max <= min => Some(min),
204 _ => None,
205 });
206
207 let styled_based_known_dimensions =
209 known_dimensions.or(min_max_definite_size.or(clamped_style_size).maybe_max(padding_border_sum));
210
211 if run_mode == RunMode::ComputeSize {
214 if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
215 return LayoutOutput::from_outer_size(Size { width, height });
216 }
217 }
218
219 debug_log!("FLEX:", dbg:style.flex_direction());
220 drop(style);
221
222 compute_preliminary(tree, node, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
223}
224
225fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
227 let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
228
229 let mut constants = compute_constants(tree.get_flexbox_container_style(node), known_dimensions, parent_size);
231
232 debug_log!("generate_anonymous_flex_items");
238 let mut flex_items = generate_anonymous_flex_items(tree, node, &constants);
239
240 debug_log!("determine_available_space");
244 let available_space = determine_available_space(known_dimensions, available_space, &constants);
245
246 debug_log!("determine_flex_base_size");
248 determine_flex_base_size(tree, &constants, available_space, &mut flex_items);
249
250 #[cfg(feature = "debug")]
251 for item in flex_items.iter() {
252 debug_log!("item.flex_basis", item.flex_basis);
253 debug_log!("item.inner_flex_basis", item.inner_flex_basis);
254 debug_log!("item.hypothetical_outer_size", dbg:item.hypothetical_outer_size);
255 debug_log!("item.hypothetical_inner_size", dbg:item.hypothetical_inner_size);
256 debug_log!("item.resolved_minimum_main_size", dbg:item.resolved_minimum_main_size);
257 }
258
259 debug_log!("collect_flex_lines");
266 let mut flex_lines = collect_flex_lines(&constants, available_space, &mut flex_items);
267
268 debug_log!("determine_container_main_size");
271 if let Some(inner_main_size) = constants.node_inner_size.main(constants.dir) {
272 let outer_main_size = inner_main_size + constants.content_box_inset.main_axis_sum(constants.dir);
273 constants.inner_container_size.set_main(constants.dir, inner_main_size);
274 constants.container_size.set_main(constants.dir, outer_main_size);
275 } else {
276 determine_container_main_size(tree, available_space, &mut flex_lines, &mut constants);
278 constants.node_inner_size.set_main(constants.dir, Some(constants.inner_container_size.main(constants.dir)));
279 constants.node_outer_size.set_main(constants.dir, Some(constants.container_size.main(constants.dir)));
280
281 debug_log!("constants.node_outer_size", dbg:constants.node_outer_size);
282 debug_log!("constants.node_inner_size", dbg:constants.node_inner_size);
283
284 let style = tree.get_flexbox_container_style(node);
286 let inner_container_size = constants.inner_container_size.main(constants.dir);
287 let new_gap = style.gap().main(constants.dir).maybe_resolve(inner_container_size).unwrap_or(0.0);
288 constants.gap.set_main(constants.dir, new_gap);
289 }
290
291 debug_log!("resolve_flexible_lengths");
293 for line in &mut flex_lines {
294 resolve_flexible_lengths(line, &constants);
295 }
296
297 debug_log!("determine_hypothetical_cross_size");
301 for line in &mut flex_lines {
302 determine_hypothetical_cross_size(tree, line, &constants, available_space);
303 }
304
305 debug_log!("calculate_children_base_lines");
308 calculate_children_base_lines(tree, known_dimensions, available_space, &mut flex_lines, &constants);
309
310 debug_log!("calculate_cross_size");
312 calculate_cross_size(&mut flex_lines, known_dimensions, &constants);
313
314 debug_log!("handle_align_content_stretch");
316 handle_align_content_stretch(&mut flex_lines, known_dimensions, &constants);
317
318 debug_log!("determine_used_cross_size");
335 determine_used_cross_size(tree, &mut flex_lines, &constants);
336
337 debug_log!("distribute_remaining_free_space");
341 distribute_remaining_free_space(&mut flex_lines, &constants);
342
343 debug_log!("resolve_cross_axis_auto_margins");
347 resolve_cross_axis_auto_margins(&mut flex_lines, &constants);
348
349 debug_log!("determine_container_cross_size");
351 let total_line_cross_size = determine_container_cross_size(&flex_lines, known_dimensions, &mut constants);
352
353 if run_mode == RunMode::ComputeSize {
356 return LayoutOutput::from_outer_size(constants.container_size);
357 }
358
359 debug_log!("align_flex_lines_per_align_content");
361 align_flex_lines_per_align_content(&mut flex_lines, &constants, total_line_cross_size);
362
363 debug_log!("final_layout_pass");
365 let inflow_content_size = final_layout_pass(tree, &mut flex_lines, &constants);
366
367 debug_log!("perform_absolute_layout_on_absolute_children");
369 let absolute_content_size = perform_absolute_layout_on_absolute_children(tree, node, &constants);
370
371 debug_log!("hidden_layout");
372 let len = tree.child_count(node);
373 for order in 0..len {
374 let child = tree.get_child_id(node, order);
375 if tree.get_flexbox_child_style(child).box_generation_mode() == BoxGenerationMode::None {
376 tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
377 tree.perform_child_layout(
378 child,
379 Size::NONE,
380 Size::NONE,
381 Size::MAX_CONTENT,
382 SizingMode::InherentSize,
383 Line::FALSE,
384 );
385 }
386 }
387
388 let first_vertical_baseline = if flex_lines.is_empty() {
391 None
392 } else {
393 flex_lines[0]
394 .items
395 .iter()
396 .find(|item| constants.is_column || item.align_self == AlignSelf::Baseline)
397 .or_else(|| flex_lines[0].items.iter().next())
398 .map(|child| {
399 let offset_vertical = if constants.is_row { child.offset_cross } else { child.offset_main };
400 offset_vertical + child.baseline
401 })
402 };
403
404 LayoutOutput::from_sizes_and_baselines(
405 constants.container_size,
406 inflow_content_size.f32_max(absolute_content_size),
407 Point { x: None, y: first_vertical_baseline },
408 )
409}
410
411#[inline]
413fn compute_constants(
414 style: impl FlexboxContainerStyle,
415 known_dimensions: Size<Option<f32>>,
416 parent_size: Size<Option<f32>>,
417) -> AlgoConstants {
418 let dir = style.flex_direction();
419 let is_row = dir.is_row();
420 let is_column = dir.is_column();
421 let is_wrap = matches!(style.flex_wrap(), FlexWrap::Wrap | FlexWrap::WrapReverse);
422 let is_wrap_reverse = style.flex_wrap() == FlexWrap::WrapReverse;
423
424 let aspect_ratio = style.aspect_ratio();
425 let margin = style.margin().resolve_or_zero(parent_size.width);
426 let padding = style.padding().resolve_or_zero(parent_size.width);
427 let border = style.border().resolve_or_zero(parent_size.width);
428 let padding_border_sum = padding.sum_axes() + border.sum_axes();
429 let box_sizing_adjustment =
430 if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
431
432 let align_items = style.align_items().unwrap_or(AlignItems::Stretch);
433 let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
434 let justify_content = style.justify_content();
435
436 let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
440 Overflow::Scroll => style.scrollbar_width(),
441 _ => 0.0,
442 });
443 let mut content_box_inset = padding + border;
445 content_box_inset.right += scrollbar_gutter.x;
446 content_box_inset.bottom += scrollbar_gutter.y;
447
448 let node_outer_size = known_dimensions;
449 let node_inner_size = node_outer_size.maybe_sub(content_box_inset.sum_axes());
450 let gap = style.gap().resolve_or_zero(node_inner_size.or(Size::zero()));
451
452 let container_size = Size::zero();
453 let inner_container_size = Size::zero();
454
455 AlgoConstants {
456 dir,
457 is_row,
458 is_column,
459 is_wrap,
460 is_wrap_reverse,
461 min_size: style
462 .min_size()
463 .maybe_resolve(parent_size)
464 .maybe_apply_aspect_ratio(aspect_ratio)
465 .maybe_add(box_sizing_adjustment),
466 max_size: style
467 .max_size()
468 .maybe_resolve(parent_size)
469 .maybe_apply_aspect_ratio(aspect_ratio)
470 .maybe_add(box_sizing_adjustment),
471 margin,
472 border,
473 gap,
474 content_box_inset,
475 scrollbar_gutter,
476 align_items,
477 align_content,
478 justify_content,
479 node_outer_size,
480 node_inner_size,
481 container_size,
482 inner_container_size,
483 }
484}
485
486#[inline]
492fn generate_anonymous_flex_items(
493 tree: &impl LayoutFlexboxContainer,
494 node: NodeId,
495 constants: &AlgoConstants,
496) -> Vec<FlexItem> {
497 tree.child_ids(node)
498 .enumerate()
499 .map(|(index, child)| (index, child, tree.get_flexbox_child_style(child)))
500 .filter(|(_, _, style)| style.position() != Position::Absolute)
501 .filter(|(_, _, style)| style.box_generation_mode() != BoxGenerationMode::None)
502 .map(|(index, child, child_style)| {
503 let aspect_ratio = child_style.aspect_ratio();
504 let padding = child_style.padding().resolve_or_zero(constants.node_inner_size.width);
505 let border = child_style.border().resolve_or_zero(constants.node_inner_size.width);
506 let pb_sum = (padding + border).sum_axes();
507 let box_sizing_adjustment =
508 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
509 FlexItem {
510 node: child,
511 order: index as u32,
512 size: child_style
513 .size()
514 .maybe_resolve(constants.node_inner_size)
515 .maybe_apply_aspect_ratio(aspect_ratio)
516 .maybe_add(box_sizing_adjustment),
517 min_size: child_style
518 .min_size()
519 .maybe_resolve(constants.node_inner_size)
520 .maybe_apply_aspect_ratio(aspect_ratio)
521 .maybe_add(box_sizing_adjustment),
522 max_size: child_style
523 .max_size()
524 .maybe_resolve(constants.node_inner_size)
525 .maybe_apply_aspect_ratio(aspect_ratio)
526 .maybe_add(box_sizing_adjustment),
527
528 inset: child_style.inset().zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
529 margin: child_style.margin().resolve_or_zero(constants.node_inner_size.width),
530 margin_is_auto: child_style.margin().map(|m| m == LengthPercentageAuto::Auto),
531 padding: child_style.padding().resolve_or_zero(constants.node_inner_size.width),
532 border: child_style.border().resolve_or_zero(constants.node_inner_size.width),
533 align_self: child_style.align_self().unwrap_or(constants.align_items),
534 overflow: child_style.overflow(),
535 scrollbar_width: child_style.scrollbar_width(),
536 flex_grow: child_style.flex_grow(),
537 flex_shrink: child_style.flex_shrink(),
538 flex_basis: 0.0,
539 inner_flex_basis: 0.0,
540 violation: 0.0,
541 frozen: false,
542
543 resolved_minimum_main_size: 0.0,
544 hypothetical_inner_size: Size::zero(),
545 hypothetical_outer_size: Size::zero(),
546 target_size: Size::zero(),
547 outer_target_size: Size::zero(),
548 content_flex_fraction: 0.0,
549
550 baseline: 0.0,
551
552 offset_main: 0.0,
553 offset_cross: 0.0,
554 }
555 })
556 .collect()
557}
558
559#[inline]
570#[must_use]
571fn determine_available_space(
572 known_dimensions: Size<Option<f32>>,
573 outer_available_space: Size<AvailableSpace>,
574 constants: &AlgoConstants,
575) -> Size<AvailableSpace> {
576 let width = match known_dimensions.width {
578 Some(node_width) => AvailableSpace::Definite(node_width - constants.content_box_inset.horizontal_axis_sum()),
579 None => outer_available_space
580 .width
581 .maybe_sub(constants.margin.horizontal_axis_sum())
582 .maybe_sub(constants.content_box_inset.horizontal_axis_sum()),
583 };
584
585 let height = match known_dimensions.height {
586 Some(node_height) => AvailableSpace::Definite(node_height - constants.content_box_inset.vertical_axis_sum()),
587 None => outer_available_space
588 .height
589 .maybe_sub(constants.margin.vertical_axis_sum())
590 .maybe_sub(constants.content_box_inset.vertical_axis_sum()),
591 };
592
593 Size { width, height }
594}
595
596#[inline]
624fn determine_flex_base_size(
625 tree: &mut impl LayoutFlexboxContainer,
626 constants: &AlgoConstants,
627 available_space: Size<AvailableSpace>,
628 flex_items: &mut [FlexItem],
629) {
630 let dir = constants.dir;
631
632 for child in flex_items.iter_mut() {
633 let child_style = tree.get_flexbox_child_style(child.node);
634
635 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
637 let child_parent_size = Size::from_cross(dir, cross_axis_parent_size);
638
639 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
641 let child_min_cross = child.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
642 let child_max_cross = child.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
643
644 let cross_axis_available_space: AvailableSpace = match available_space.cross(dir) {
646 AvailableSpace::Definite(val) => AvailableSpace::Definite(
647 cross_axis_parent_size.unwrap_or(val).maybe_clamp(child_min_cross, child_max_cross),
648 ),
649 AvailableSpace::MinContent => match child_min_cross {
650 Some(min) => AvailableSpace::Definite(min),
651 None => AvailableSpace::MinContent,
652 },
653 AvailableSpace::MaxContent => match child_max_cross {
654 Some(max) => AvailableSpace::Definite(max),
655 None => AvailableSpace::MaxContent,
656 },
657 };
658
659 let child_known_dimensions = {
661 let mut ckd = child.size.with_main(dir, None);
662 if child.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
663 ckd.set_cross(
664 dir,
665 cross_axis_available_space.into_option().maybe_sub(child.margin.cross_axis_sum(dir)),
666 );
667 }
668 ckd
669 };
670
671 let container_width = constants.node_inner_size.main(dir);
672 let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox {
673 let padding = child_style.padding().resolve_or_zero(container_width);
674 let border = child_style.border().resolve_or_zero(container_width);
675 (padding + border).sum_axes()
676 } else {
677 Size::ZERO
678 }
679 .main(dir);
680 let flex_basis = child_style.flex_basis().maybe_resolve(container_width).maybe_add(box_sizing_adjustment);
681
682 drop(child_style);
683
684 child.flex_basis = 'flex_basis: {
685 let main_size = child.size.main(dir);
695 if let Some(flex_basis) = flex_basis.or(main_size) {
696 break 'flex_basis flex_basis;
697 };
698
699 let child_available_space = Size::MAX_CONTENT
724 .with_main(
725 dir,
726 if available_space.main(dir) == AvailableSpace::MinContent {
728 AvailableSpace::MinContent
729 } else {
730 AvailableSpace::MaxContent
731 },
732 )
733 .with_cross(dir, cross_axis_available_space);
734
735 debug_log!("COMPUTE CHILD BASE SIZE:");
736 break 'flex_basis tree.measure_child_size(
737 child.node,
738 child_known_dimensions,
739 child_parent_size,
740 child_available_space,
741 SizingMode::ContentSize,
742 dir.main_axis(),
743 Line::FALSE,
744 );
745 };
746
747 let padding_border_sum = child.padding.main_axis_sum(constants.dir) + child.border.main_axis_sum(constants.dir);
755 child.flex_basis = child.flex_basis.max(padding_border_sum);
756
757 child.inner_flex_basis =
761 child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
762
763 let padding_border_axes_sums = (child.padding + child.border).sum_axes().map(Some);
764
765 let style_min_main_size =
772 child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);
773
774 child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
775 let min_content_main_size = {
776 let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
777
778 debug_log!("COMPUTE CHILD MIN SIZE:");
779 tree.measure_child_size(
780 child.node,
781 child_known_dimensions,
782 child_parent_size,
783 child_available_space,
784 SizingMode::ContentSize,
785 dir.main_axis(),
786 Line::FALSE,
787 )
788 };
789
790 let clamped_min_content_size =
793 min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
794 clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
795 });
796
797 let hypothetical_inner_min_main =
798 child.resolved_minimum_main_size.maybe_max(padding_border_axes_sums.main(constants.dir));
799 let hypothetical_inner_size =
800 child.flex_basis.maybe_clamp(Some(hypothetical_inner_min_main), child.max_size.main(constants.dir));
801 let hypothetical_outer_size = hypothetical_inner_size + child.margin.main_axis_sum(constants.dir);
802
803 child.hypothetical_inner_size.set_main(constants.dir, hypothetical_inner_size);
804 child.hypothetical_outer_size.set_main(constants.dir, hypothetical_outer_size);
805 }
806}
807
808#[inline]
826fn collect_flex_lines<'a>(
827 constants: &AlgoConstants,
828 available_space: Size<AvailableSpace>,
829 flex_items: &'a mut Vec<FlexItem>,
830) -> Vec<FlexLine<'a>> {
831 if !constants.is_wrap {
832 let mut lines = new_vec_with_capacity(1);
833 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
834 lines
835 } else {
836 let main_axis_available_space = match constants.max_size.main(constants.dir) {
837 Some(max_size) => AvailableSpace::Definite(
838 available_space
839 .main(constants.dir)
840 .into_option()
841 .unwrap_or(max_size)
842 .maybe_max(constants.min_size.main(constants.dir)),
843 ),
844 None => available_space.main(constants.dir),
845 };
846
847 match main_axis_available_space {
848 AvailableSpace::MaxContent => {
851 let mut lines = new_vec_with_capacity(1);
852 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
853 lines
854 }
855 AvailableSpace::MinContent => {
858 let mut lines = new_vec_with_capacity(flex_items.len());
859 let mut items = &mut flex_items[..];
860 while !items.is_empty() {
861 let (line_items, rest) = items.split_at_mut(1);
862 lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
863 items = rest;
864 }
865 lines
866 }
867 AvailableSpace::Definite(main_axis_available_space) => {
868 let mut lines = new_vec_with_capacity(1);
869 let mut flex_items = &mut flex_items[..];
870 let main_axis_gap = constants.gap.main(constants.dir);
871
872 while !flex_items.is_empty() {
873 let mut line_length = 0.0;
876 let index = flex_items
877 .iter()
878 .enumerate()
879 .find(|&(idx, child)| {
880 let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
883 line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
884 line_length > main_axis_available_space && idx != 0
885 })
886 .map(|(idx, _)| idx)
887 .unwrap_or(flex_items.len());
888
889 let (items, rest) = flex_items.split_at_mut(index);
890 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
891 flex_items = rest;
892 }
893 lines
894 }
895 }
896 }
897}
898
899fn determine_container_main_size(
901 tree: &mut impl LayoutFlexboxContainer,
902 available_space: Size<AvailableSpace>,
903 lines: &mut [FlexLine<'_>],
904 constants: &mut AlgoConstants,
905) {
906 let dir = constants.dir;
907 let main_content_box_inset = constants.content_box_inset.main_axis_sum(constants.dir);
908
909 let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
910 match available_space.main(dir) {
911 AvailableSpace::Definite(main_axis_available_space) => {
912 let longest_line_length: f32 = lines
913 .iter()
914 .map(|line| {
915 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
916 let total_target_size = line
917 .items
918 .iter()
919 .map(|child| {
920 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
921 (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
922 + child.margin.main_axis_sum(constants.dir))
923 .max(padding_border_sum)
924 })
925 .sum::<f32>();
926 total_target_size + line_main_axis_gap
927 })
928 .max_by(|a, b| a.total_cmp(b))
929 .unwrap_or(0.0);
930 let size = longest_line_length + main_content_box_inset;
931 if lines.len() > 1 {
932 f32_max(size, main_axis_available_space)
933 } else {
934 size
935 }
936 }
937 AvailableSpace::MinContent if constants.is_wrap => {
938 let longest_line_length: f32 = lines
939 .iter()
940 .map(|line| {
941 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
942 let total_target_size = line
943 .items
944 .iter()
945 .map(|child| {
946 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
947 (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
948 + child.margin.main_axis_sum(constants.dir))
949 .max(padding_border_sum)
950 })
951 .sum::<f32>();
952 total_target_size + line_main_axis_gap
953 })
954 .max_by(|a, b| a.total_cmp(b))
955 .unwrap_or(0.0);
956 longest_line_length + main_content_box_inset
957 }
958 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
959 let mut main_size = 0.0;
963
964 for line in lines.iter_mut() {
965 for item in line.items.iter_mut() {
966 let style_min = item.min_size.main(constants.dir);
967 let style_preferred = item.size.main(constants.dir);
968 let style_max = item.max_size.main(constants.dir);
969
970 let clamping_basis = Some(item.flex_basis).maybe_max(style_preferred);
978 let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
979 let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);
980
981 let min_main_size = style_min
982 .maybe_max(flex_basis_min)
983 .or(flex_basis_min)
984 .unwrap_or(item.resolved_minimum_main_size)
985 .max(item.resolved_minimum_main_size);
986 let max_main_size =
987 style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);
988
989 let content_contribution = match (min_main_size, style_preferred, max_main_size) {
990 (min, Some(pref), max) if max <= min || max <= pref => {
993 pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
994 }
995 (min, _, max) if max <= min => min + item.margin.main_axis_sum(constants.dir),
996
997 _ if item.is_scroll_container() => {
1000 item.flex_basis + item.margin.main_axis_sum(constants.dir)
1001 }
1002 _ => {
1003 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
1005
1006 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
1008 let child_min_cross = item.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
1009 let child_max_cross = item.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
1010 let cross_axis_available_space: AvailableSpace = available_space
1011 .cross(dir)
1012 .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
1013 .maybe_clamp(child_min_cross, child_max_cross);
1014
1015 let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
1016
1017 let child_known_dimensions = {
1019 let mut ckd = item.size.with_main(dir, None);
1020 if item.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
1021 ckd.set_cross(
1022 dir,
1023 cross_axis_available_space
1024 .into_option()
1025 .maybe_sub(item.margin.cross_axis_sum(dir)),
1026 );
1027 }
1028 ckd
1029 };
1030
1031 debug_log!("COMPUTE CHILD BASE SIZE (for intrinsic main size):");
1034 let content_main_size = tree.measure_child_size(
1035 item.node,
1036 child_known_dimensions,
1037 constants.node_inner_size,
1038 child_available_space,
1039 SizingMode::InherentSize,
1040 dir.main_axis(),
1041 Line::FALSE,
1042 ) + item.margin.main_axis_sum(constants.dir);
1043
1044 if constants.is_row {
1057 content_main_size.maybe_clamp(style_min, style_max).max(main_content_box_inset)
1058 } else {
1059 content_main_size
1060 .max(item.flex_basis)
1061 .maybe_clamp(style_min, style_max)
1062 .max(main_content_box_inset)
1063 }
1064 }
1065 };
1066 item.content_flex_fraction = {
1067 let diff = content_contribution - item.flex_basis;
1068 if diff > 0.0 {
1069 diff / f32_max(1.0, item.flex_grow)
1070 } else if diff < 0.0 {
1071 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
1072 diff / scaled_shrink_factor
1073 } else {
1074 0.0
1076 }
1077 };
1078 }
1079
1080 let item_main_size_sum = line
1098 .items
1099 .iter_mut()
1100 .map(|item| {
1101 let flex_fraction = item.content_flex_fraction;
1102 let flex_contribution = if item.content_flex_fraction > 0.0 {
1105 f32_max(1.0, item.flex_grow) * flex_fraction
1106 } else if item.content_flex_fraction < 0.0 {
1107 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
1108 scaled_shrink_factor * flex_fraction
1109 } else {
1110 0.0
1111 };
1112 let size = item.flex_basis + flex_contribution;
1113 item.outer_target_size.set_main(constants.dir, size);
1114 item.target_size.set_main(constants.dir, size);
1115 size
1116 })
1117 .sum::<f32>();
1118
1119 let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1120 main_size = f32_max(main_size, item_main_size_sum + gap_sum)
1121 }
1122
1123 main_size + main_content_box_inset
1124 }
1125 }
1126 });
1127
1128 let outer_main_size = outer_main_size
1129 .maybe_clamp(constants.min_size.main(constants.dir), constants.max_size.main(constants.dir))
1130 .max(main_content_box_inset - constants.scrollbar_gutter.main(constants.dir));
1131
1132 let inner_main_size = f32_max(outer_main_size - main_content_box_inset, 0.0);
1134 constants.container_size.set_main(constants.dir, outer_main_size);
1135 constants.inner_container_size.set_main(constants.dir, inner_main_size);
1136 constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
1137}
1138
1139#[inline]
1144fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants) {
1145 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1146
1147 let total_hypothetical_outer_main_size =
1153 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
1154 let used_flex_factor: f32 = total_main_axis_gap + total_hypothetical_outer_main_size;
1155 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1156 let shrinking = used_flex_factor > constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1157 let exactly_sized = !growing & !shrinking;
1158
1159 for child in line.items.iter_mut() {
1167 let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
1168 child.target_size.set_main(constants.dir, inner_target_size);
1169
1170 if exactly_sized
1171 || (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
1172 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1173 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1174 {
1175 child.frozen = true;
1176 let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1177 child.outer_target_size.set_main(constants.dir, outer_target_size);
1178 }
1179 }
1180
1181 if exactly_sized {
1182 return;
1183 }
1184
1185 let used_space: f32 = total_main_axis_gap
1190 + line
1191 .items
1192 .iter()
1193 .map(|child| {
1194 if child.frozen {
1195 child.outer_target_size.main(constants.dir)
1196 } else {
1197 child.flex_basis + child.margin.main_axis_sum(constants.dir)
1198 }
1199 })
1200 .sum::<f32>();
1201
1202 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1203
1204 loop {
1207 if line.items.iter().all(|child| child.frozen) {
1211 break;
1212 }
1213
1214 let used_space: f32 = total_main_axis_gap
1221 + line
1222 .items
1223 .iter()
1224 .map(|child| {
1225 if child.frozen {
1226 child.outer_target_size.main(constants.dir)
1227 } else {
1228 child.flex_basis + child.margin.main_axis_sum(constants.dir)
1229 }
1230 })
1231 .sum::<f32>();
1232
1233 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1234
1235 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1236 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1237 (flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
1238 });
1239
1240 let free_space = if growing && sum_flex_grow < 1.0 {
1241 (initial_free_space * sum_flex_grow - total_main_axis_gap)
1242 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1243 } else if shrinking && sum_flex_shrink < 1.0 {
1244 (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1245 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1246 } else {
1247 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1248 .unwrap_or(used_flex_factor - used_space)
1249 };
1250
1251 if free_space.is_normal() {
1271 if growing && sum_flex_grow > 0.0 {
1272 for child in &mut unfrozen {
1273 child
1274 .target_size
1275 .set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
1276 }
1277 } else if shrinking && sum_flex_shrink > 0.0 {
1278 let sum_scaled_shrink_factor: f32 =
1279 unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
1280
1281 if sum_scaled_shrink_factor > 0.0 {
1282 for child in &mut unfrozen {
1283 let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
1284 child.target_size.set_main(
1285 constants.dir,
1286 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1287 )
1288 }
1289 }
1290 }
1291 }
1292
1293 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1299 let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
1300 let max_main = child.max_size.main(constants.dir);
1301 let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1302 child.violation = clamped - child.target_size.main(constants.dir);
1303 child.target_size.set_main(constants.dir, clamped);
1304 child.outer_target_size.set_main(
1305 constants.dir,
1306 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1307 );
1308
1309 acc + child.violation
1310 });
1311
1312 for child in &mut unfrozen {
1322 match total_violation {
1323 v if v > 0.0 => child.frozen = child.violation > 0.0,
1324 v if v < 0.0 => child.frozen = child.violation < 0.0,
1325 _ => child.frozen = true,
1326 }
1327 }
1328
1329 }
1331}
1332
1333#[inline]
1340fn determine_hypothetical_cross_size(
1341 tree: &mut impl LayoutFlexboxContainer,
1342 line: &mut FlexLine,
1343 constants: &AlgoConstants,
1344 available_space: Size<AvailableSpace>,
1345) {
1346 for child in line.items.iter_mut() {
1347 let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
1348
1349 let child_known_main = constants.container_size.main(constants.dir).into();
1350
1351 let child_cross = child
1352 .size
1353 .cross(constants.dir)
1354 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1355 .maybe_max(padding_border_sum);
1356
1357 let child_available_cross = available_space
1358 .cross(constants.dir)
1359 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1360 .maybe_max(padding_border_sum);
1361
1362 let child_inner_cross = child_cross.unwrap_or_else(|| {
1363 tree.measure_child_size(
1364 child.node,
1365 Size {
1366 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1367 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1368 },
1369 constants.node_inner_size,
1370 Size {
1371 width: if constants.is_row { child_known_main } else { child_available_cross },
1372 height: if constants.is_row { child_available_cross } else { child_known_main },
1373 },
1374 SizingMode::ContentSize,
1375 constants.dir.cross_axis(),
1376 Line::FALSE,
1377 )
1378 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1379 .max(padding_border_sum)
1380 });
1381 let child_outer_cross = child_inner_cross + child.margin.cross_axis_sum(constants.dir);
1382
1383 child.hypothetical_inner_size.set_cross(constants.dir, child_inner_cross);
1384 child.hypothetical_outer_size.set_cross(constants.dir, child_outer_cross);
1385 }
1386}
1387
1388#[inline]
1390fn calculate_children_base_lines(
1391 tree: &mut impl LayoutFlexboxContainer,
1392 node_size: Size<Option<f32>>,
1393 available_space: Size<AvailableSpace>,
1394 flex_lines: &mut [FlexLine],
1395 constants: &AlgoConstants,
1396) {
1397 if !constants.is_row {
1401 return;
1402 }
1403
1404 for line in flex_lines {
1405 let line_baseline_child_count =
1407 line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1408 if line_baseline_child_count <= 1 {
1409 continue;
1410 }
1411
1412 for child in line.items.iter_mut() {
1413 if child.align_self != AlignSelf::Baseline {
1415 continue;
1416 }
1417
1418 let measured_size_and_baselines = tree.perform_child_layout(
1419 child.node,
1420 Size {
1421 width: if constants.is_row {
1422 child.target_size.width.into()
1423 } else {
1424 child.hypothetical_inner_size.width.into()
1425 },
1426 height: if constants.is_row {
1427 child.hypothetical_inner_size.height.into()
1428 } else {
1429 child.target_size.height.into()
1430 },
1431 },
1432 constants.node_inner_size,
1433 Size {
1434 width: if constants.is_row {
1435 constants.container_size.width.into()
1436 } else {
1437 available_space.width.maybe_set(node_size.width)
1438 },
1439 height: if constants.is_row {
1440 available_space.height.maybe_set(node_size.height)
1441 } else {
1442 constants.container_size.height.into()
1443 },
1444 },
1445 SizingMode::ContentSize,
1446 Line::FALSE,
1447 );
1448
1449 let baseline = measured_size_and_baselines.first_baselines.y;
1450 let height = measured_size_and_baselines.size.height;
1451
1452 child.baseline = baseline.unwrap_or(height) + child.margin.top;
1453 }
1454 }
1455}
1456
1457#[inline]
1463fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1464 if !constants.is_wrap && node_size.cross(constants.dir).is_some() {
1467 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1468 let cross_min_size = constants.min_size.cross(constants.dir);
1469 let cross_max_size = constants.max_size.cross(constants.dir);
1470 flex_lines[0].cross_size = node_size
1471 .cross(constants.dir)
1472 .maybe_clamp(cross_min_size, cross_max_size)
1473 .maybe_sub(cross_axis_padding_border)
1474 .maybe_max(0.0)
1475 .unwrap_or(0.0);
1476 } else {
1477 for line in flex_lines.iter_mut() {
1491 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1492 line.cross_size = line
1493 .items
1494 .iter()
1495 .map(|child| {
1496 if child.align_self == AlignSelf::Baseline
1497 && !child.margin_is_auto.cross_start(constants.dir)
1498 && !child.margin_is_auto.cross_end(constants.dir)
1499 {
1500 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1501 } else {
1502 child.hypothetical_outer_size.cross(constants.dir)
1503 }
1504 })
1505 .fold(0.0, |acc, x| acc.max(x));
1506 }
1507
1508 if !constants.is_wrap {
1511 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1512 let cross_min_size = constants.min_size.cross(constants.dir);
1513 let cross_max_size = constants.max_size.cross(constants.dir);
1514 flex_lines[0].cross_size = flex_lines[0].cross_size.maybe_clamp(
1515 cross_min_size.maybe_sub(cross_axis_padding_border),
1516 cross_max_size.maybe_sub(cross_axis_padding_border),
1517 );
1518 }
1519 }
1520}
1521
1522#[inline]
1530fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1531 if constants.align_content == AlignContent::Stretch {
1532 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1533 let cross_min_size = constants.min_size.cross(constants.dir);
1534 let cross_max_size = constants.max_size.cross(constants.dir);
1535 let container_min_inner_cross = node_size
1536 .cross(constants.dir)
1537 .or(cross_min_size)
1538 .maybe_clamp(cross_min_size, cross_max_size)
1539 .maybe_sub(cross_axis_padding_border)
1540 .maybe_max(0.0)
1541 .unwrap_or(0.0);
1542
1543 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1544 let lines_total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1545
1546 if lines_total_cross < container_min_inner_cross {
1547 let remaining = container_min_inner_cross - lines_total_cross;
1548 let addition = remaining / flex_lines.len() as f32;
1549 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1550 }
1551 }
1552}
1553
1554#[inline]
1566fn determine_used_cross_size(
1567 tree: &impl LayoutFlexboxContainer,
1568 flex_lines: &mut [FlexLine],
1569 constants: &AlgoConstants,
1570) {
1571 for line in flex_lines {
1572 let line_cross_size = line.cross_size;
1573
1574 for child in line.items.iter_mut() {
1575 let child_style = tree.get_flexbox_child_style(child.node);
1576 child.target_size.set_cross(
1577 constants.dir,
1578 if child.align_self == AlignSelf::Stretch
1579 && !child.margin_is_auto.cross_start(constants.dir)
1580 && !child.margin_is_auto.cross_end(constants.dir)
1581 && child_style.size().cross(constants.dir) == Dimension::Auto
1582 {
1583 let padding = child_style.padding().resolve_or_zero(constants.node_inner_size);
1587 let border = child_style.border().resolve_or_zero(constants.node_inner_size);
1588 let pb_sum = (padding + border).sum_axes();
1589 let box_sizing_adjustment =
1590 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
1591
1592 let max_size_ignoring_aspect_ratio = child_style
1593 .max_size()
1594 .maybe_resolve(constants.node_inner_size)
1595 .maybe_add(box_sizing_adjustment);
1596
1597 (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1598 child.min_size.cross(constants.dir),
1599 max_size_ignoring_aspect_ratio.cross(constants.dir),
1600 )
1601 } else {
1602 child.hypothetical_inner_size.cross(constants.dir)
1603 },
1604 );
1605
1606 child.outer_target_size.set_cross(
1607 constants.dir,
1608 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1609 );
1610 }
1611 }
1612}
1613
1614#[inline]
1625fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1626 for line in flex_lines {
1627 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1628 let used_space: f32 = total_main_axis_gap
1629 + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1630 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1631 let mut num_auto_margins = 0;
1632
1633 for child in line.items.iter_mut() {
1634 if child.margin_is_auto.main_start(constants.dir) {
1635 num_auto_margins += 1;
1636 }
1637 if child.margin_is_auto.main_end(constants.dir) {
1638 num_auto_margins += 1;
1639 }
1640 }
1641
1642 if free_space > 0.0 && num_auto_margins > 0 {
1643 let margin = free_space / num_auto_margins as f32;
1644
1645 for child in line.items.iter_mut() {
1646 if child.margin_is_auto.main_start(constants.dir) {
1647 if constants.is_row {
1648 child.margin.left = margin;
1649 } else {
1650 child.margin.top = margin;
1651 }
1652 }
1653 if child.margin_is_auto.main_end(constants.dir) {
1654 if constants.is_row {
1655 child.margin.right = margin;
1656 } else {
1657 child.margin.bottom = margin;
1658 }
1659 }
1660 }
1661 } else {
1662 let num_items = line.items.len();
1663 let layout_reverse = constants.dir.is_reverse();
1664 let gap = constants.gap.main(constants.dir);
1665 let is_safe = false; let raw_justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
1667 let justify_content_mode =
1668 apply_alignment_fallback(free_space, num_items, raw_justify_content_mode, is_safe);
1669
1670 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1671 child.offset_main =
1672 compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1673 };
1674
1675 if layout_reverse {
1676 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1677 } else {
1678 line.items.iter_mut().enumerate().for_each(justify_item);
1679 }
1680 }
1681 }
1682}
1683
1684#[inline]
1697fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1698 for line in flex_lines {
1699 let line_cross_size = line.cross_size;
1700 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1701
1702 for child in line.items.iter_mut() {
1703 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1704
1705 if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
1706 if constants.is_row {
1707 child.margin.top = free_space / 2.0;
1708 child.margin.bottom = free_space / 2.0;
1709 } else {
1710 child.margin.left = free_space / 2.0;
1711 child.margin.right = free_space / 2.0;
1712 }
1713 } else if child.margin_is_auto.cross_start(constants.dir) {
1714 if constants.is_row {
1715 child.margin.top = free_space;
1716 } else {
1717 child.margin.left = free_space;
1718 }
1719 } else if child.margin_is_auto.cross_end(constants.dir) {
1720 if constants.is_row {
1721 child.margin.bottom = free_space;
1722 } else {
1723 child.margin.right = free_space;
1724 }
1725 } else {
1726 child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1728 }
1729 }
1730 }
1731}
1732
1733#[inline]
1740fn align_flex_items_along_cross_axis(
1741 child: &FlexItem,
1742 free_space: f32,
1743 max_baseline: f32,
1744 constants: &AlgoConstants,
1745) -> f32 {
1746 match child.align_self {
1747 AlignSelf::Start => 0.0,
1748 AlignSelf::FlexStart => {
1749 if constants.is_wrap_reverse {
1750 free_space
1751 } else {
1752 0.0
1753 }
1754 }
1755 AlignSelf::End => free_space,
1756 AlignSelf::FlexEnd => {
1757 if constants.is_wrap_reverse {
1758 0.0
1759 } else {
1760 free_space
1761 }
1762 }
1763 AlignSelf::Center => free_space / 2.0,
1764 AlignSelf::Baseline => {
1765 if constants.is_row {
1766 max_baseline - child.baseline
1767 } else {
1768 if constants.is_wrap_reverse {
1771 free_space
1772 } else {
1773 0.0
1774 }
1775 }
1776 }
1777 AlignSelf::Stretch => {
1778 if constants.is_wrap_reverse {
1779 free_space
1780 } else {
1781 0.0
1782 }
1783 }
1784 }
1785}
1786
1787#[inline]
1797#[must_use]
1798fn determine_container_cross_size(
1799 flex_lines: &[FlexLine],
1800 node_size: Size<Option<f32>>,
1801 constants: &mut AlgoConstants,
1802) -> f32 {
1803 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1804 let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1805
1806 let padding_border_sum = constants.content_box_inset.cross_axis_sum(constants.dir);
1807 let cross_scrollbar_gutter = constants.scrollbar_gutter.cross(constants.dir);
1808 let min_cross_size = constants.min_size.cross(constants.dir);
1809 let max_cross_size = constants.max_size.cross(constants.dir);
1810 let outer_container_size = node_size
1811 .cross(constants.dir)
1812 .unwrap_or(total_line_cross_size + total_cross_axis_gap + padding_border_sum)
1813 .maybe_clamp(min_cross_size, max_cross_size)
1814 .max(padding_border_sum - cross_scrollbar_gutter);
1815 let inner_container_size = f32_max(outer_container_size - padding_border_sum, 0.0);
1816
1817 constants.container_size.set_cross(constants.dir, outer_container_size);
1818 constants.inner_container_size.set_cross(constants.dir, inner_container_size);
1819
1820 total_line_cross_size
1821}
1822
1823#[inline]
1829fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
1830 let num_lines = flex_lines.len();
1831 let gap = constants.gap.cross(constants.dir);
1832 let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1833 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1834 let is_safe = false; let align_content_mode = apply_alignment_fallback(free_space, num_lines, constants.align_content, is_safe);
1837
1838 let align_line = |(i, line): (usize, &mut FlexLine)| {
1839 line.offset_cross =
1840 compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1841 };
1842
1843 if constants.is_wrap_reverse {
1844 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1845 } else {
1846 flex_lines.iter_mut().enumerate().for_each(align_line);
1847 }
1848}
1849
1850#[allow(clippy::too_many_arguments)]
1852fn calculate_flex_item(
1853 tree: &mut impl LayoutFlexboxContainer,
1854 item: &mut FlexItem,
1855 total_offset_main: &mut f32,
1856 total_offset_cross: f32,
1857 line_offset_cross: f32,
1858 #[cfg(feature = "content_size")] total_content_size: &mut Size<f32>,
1859 container_size: Size<f32>,
1860 node_inner_size: Size<Option<f32>>,
1861 direction: FlexDirection,
1862) {
1863 let layout_output = tree.perform_child_layout(
1864 item.node,
1865 item.target_size.map(|s| s.into()),
1866 node_inner_size,
1867 container_size.map(|s| s.into()),
1868 SizingMode::ContentSize,
1869 Line::FALSE,
1870 );
1871 let LayoutOutput {
1872 size,
1873 #[cfg(feature = "content_size")]
1874 content_size,
1875 ..
1876 } = layout_output;
1877
1878 let offset_main = *total_offset_main
1879 + item.offset_main
1880 + item.margin.main_start(direction)
1881 + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1882
1883 let offset_cross = total_offset_cross
1884 + item.offset_cross
1885 + line_offset_cross
1886 + item.margin.cross_start(direction)
1887 + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1888
1889 if direction.is_row() {
1890 let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1891 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1892 item.baseline = baseline_offset_cross + inner_baseline;
1893 } else {
1894 let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1895 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1896 item.baseline = baseline_offset_main + inner_baseline;
1897 }
1898
1899 let location = match direction.is_row() {
1900 true => Point { x: offset_main, y: offset_cross },
1901 false => Point { x: offset_cross, y: offset_main },
1902 };
1903 let scrollbar_size = Size {
1904 width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1905 height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1906 };
1907
1908 tree.set_unrounded_layout(
1909 item.node,
1910 &Layout {
1911 order: item.order,
1912 size,
1913 #[cfg(feature = "content_size")]
1914 content_size,
1915 scrollbar_size,
1916 location,
1917 padding: item.padding,
1918 border: item.border,
1919 margin: item.margin,
1920 },
1921 );
1922
1923 *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
1924
1925 #[cfg(feature = "content_size")]
1926 {
1927 *total_content_size =
1928 total_content_size.f32_max(compute_content_size_contribution(location, size, content_size, item.overflow));
1929 }
1930}
1931
1932#[allow(clippy::too_many_arguments)]
1934fn calculate_layout_line(
1935 tree: &mut impl LayoutFlexboxContainer,
1936 line: &mut FlexLine,
1937 total_offset_cross: &mut f32,
1938 #[cfg(feature = "content_size")] content_size: &mut Size<f32>,
1939 container_size: Size<f32>,
1940 node_inner_size: Size<Option<f32>>,
1941 padding_border: Rect<f32>,
1942 direction: FlexDirection,
1943) {
1944 let mut total_offset_main = padding_border.main_start(direction);
1945 let line_offset_cross = line.offset_cross;
1946
1947 if direction.is_reverse() {
1948 for item in line.items.iter_mut().rev() {
1949 calculate_flex_item(
1950 tree,
1951 item,
1952 &mut total_offset_main,
1953 *total_offset_cross,
1954 line_offset_cross,
1955 #[cfg(feature = "content_size")]
1956 content_size,
1957 container_size,
1958 node_inner_size,
1959 direction,
1960 );
1961 }
1962 } else {
1963 for item in line.items.iter_mut() {
1964 calculate_flex_item(
1965 tree,
1966 item,
1967 &mut total_offset_main,
1968 *total_offset_cross,
1969 line_offset_cross,
1970 #[cfg(feature = "content_size")]
1971 content_size,
1972 container_size,
1973 node_inner_size,
1974 direction,
1975 );
1976 }
1977 }
1978
1979 *total_offset_cross += line_offset_cross + line.cross_size;
1980}
1981
1982#[inline]
1984fn final_layout_pass(
1985 tree: &mut impl LayoutFlexboxContainer,
1986 flex_lines: &mut [FlexLine],
1987 constants: &AlgoConstants,
1988) -> Size<f32> {
1989 let mut total_offset_cross = constants.content_box_inset.cross_start(constants.dir);
1990
1991 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
1992 let mut content_size = Size::ZERO;
1993
1994 if constants.is_wrap_reverse {
1995 for line in flex_lines.iter_mut().rev() {
1996 calculate_layout_line(
1997 tree,
1998 line,
1999 &mut total_offset_cross,
2000 #[cfg(feature = "content_size")]
2001 &mut content_size,
2002 constants.container_size,
2003 constants.node_inner_size,
2004 constants.content_box_inset,
2005 constants.dir,
2006 );
2007 }
2008 } else {
2009 for line in flex_lines.iter_mut() {
2010 calculate_layout_line(
2011 tree,
2012 line,
2013 &mut total_offset_cross,
2014 #[cfg(feature = "content_size")]
2015 &mut content_size,
2016 constants.container_size,
2017 constants.node_inner_size,
2018 constants.content_box_inset,
2019 constants.dir,
2020 );
2021 }
2022 }
2023
2024 content_size.width += constants.content_box_inset.right - constants.border.right - constants.scrollbar_gutter.x;
2025 content_size.height += constants.content_box_inset.bottom - constants.border.bottom - constants.scrollbar_gutter.y;
2026
2027 content_size
2028}
2029
2030#[inline]
2032fn perform_absolute_layout_on_absolute_children(
2033 tree: &mut impl LayoutFlexboxContainer,
2034 node: NodeId,
2035 constants: &AlgoConstants,
2036) -> Size<f32> {
2037 let container_width = constants.container_size.width;
2038 let container_height = constants.container_size.height;
2039 let inset_relative_size =
2040 constants.container_size - constants.border.sum_axes() - constants.scrollbar_gutter.into();
2041
2042 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
2043 let mut content_size = Size::ZERO;
2044
2045 for order in 0..tree.child_count(node) {
2046 let child = tree.get_child_id(node, order);
2047 let child_style = tree.get_flexbox_child_style(child);
2048
2049 if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
2051 {
2052 continue;
2053 }
2054
2055 let overflow = child_style.overflow();
2056 let scrollbar_width = child_style.scrollbar_width();
2057 let aspect_ratio = child_style.aspect_ratio();
2058 let align_self = child_style.align_self().unwrap_or(constants.align_items);
2059 let margin = child_style.margin().map(|margin| margin.resolve_to_option(inset_relative_size.width));
2060 let padding = child_style.padding().resolve_or_zero(Some(inset_relative_size.width));
2061 let border = child_style.border().resolve_or_zero(Some(inset_relative_size.width));
2062 let padding_border_sum = (padding + border).sum_axes();
2063 let box_sizing_adjustment =
2064 if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
2065
2066 let left = child_style.inset().left.maybe_resolve(inset_relative_size.width);
2069 let right = child_style.inset().right.maybe_resolve(inset_relative_size.width);
2070 let top = child_style.inset().top.maybe_resolve(inset_relative_size.height);
2071 let bottom = child_style.inset().bottom.maybe_resolve(inset_relative_size.height);
2072
2073 let style_size = child_style
2075 .size()
2076 .maybe_resolve(inset_relative_size)
2077 .maybe_apply_aspect_ratio(aspect_ratio)
2078 .maybe_add(box_sizing_adjustment);
2079 let min_size = child_style
2080 .min_size()
2081 .maybe_resolve(inset_relative_size)
2082 .maybe_apply_aspect_ratio(aspect_ratio)
2083 .maybe_add(box_sizing_adjustment)
2084 .or(padding_border_sum.map(Some))
2085 .maybe_max(padding_border_sum);
2086 let max_size = child_style
2087 .max_size()
2088 .maybe_resolve(inset_relative_size)
2089 .maybe_apply_aspect_ratio(aspect_ratio)
2090 .maybe_add(box_sizing_adjustment);
2091 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
2092
2093 drop(child_style);
2094
2095 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
2099 let new_width_raw = inset_relative_size.width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
2100 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
2101 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2102 }
2103
2104 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
2108 let new_height_raw =
2109 inset_relative_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
2110 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
2111 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2112 }
2113 let layout_output = tree.perform_child_layout(
2114 child,
2115 known_dimensions,
2116 constants.node_inner_size,
2117 Size {
2118 width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
2119 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
2120 },
2121 SizingMode::InherentSize,
2122 Line::FALSE,
2123 );
2124 let measured_size = layout_output.size;
2125 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
2126
2127 let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
2128
2129 let free_space = Size {
2130 width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
2131 height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
2132 }
2133 .f32_max(Size::ZERO);
2134
2135 let resolved_margin = {
2137 let auto_margin_size = Size {
2138 width: {
2139 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
2140 if auto_margin_count > 0 {
2141 free_space.width / auto_margin_count as f32
2142 } else {
2143 0.0
2144 }
2145 },
2146 height: {
2147 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
2148 if auto_margin_count > 0 {
2149 free_space.height / auto_margin_count as f32
2150 } else {
2151 0.0
2152 }
2153 },
2154 };
2155
2156 Rect {
2157 left: margin.left.unwrap_or(auto_margin_size.width),
2158 right: margin.right.unwrap_or(auto_margin_size.width),
2159 top: margin.top.unwrap_or(auto_margin_size.height),
2160 bottom: margin.bottom.unwrap_or(auto_margin_size.height),
2161 }
2162 };
2163
2164 let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
2166 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
2167
2168 let offset_main = if let Some(start) = start_main {
2171 start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2172 } else if let Some(end) = end_main {
2173 constants.container_size.main(constants.dir)
2174 - constants.border.main_end(constants.dir)
2175 - constants.scrollbar_gutter.main(constants.dir)
2176 - final_size.main(constants.dir)
2177 - end
2178 - resolved_margin.main_end(constants.dir)
2179 } else {
2180 match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
2183 (JustifyContent::SpaceBetween, _)
2184 | (JustifyContent::Start, _)
2185 | (JustifyContent::Stretch, false)
2186 | (JustifyContent::FlexStart, false)
2187 | (JustifyContent::FlexEnd, true) => {
2188 constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2189 }
2190 (JustifyContent::End, _)
2191 | (JustifyContent::FlexEnd, false)
2192 | (JustifyContent::FlexStart, true)
2193 | (JustifyContent::Stretch, true) => {
2194 constants.container_size.main(constants.dir)
2195 - constants.content_box_inset.main_end(constants.dir)
2196 - final_size.main(constants.dir)
2197 - resolved_margin.main_end(constants.dir)
2198 }
2199 (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
2200 (constants.container_size.main(constants.dir)
2201 + constants.content_box_inset.main_start(constants.dir)
2202 - constants.content_box_inset.main_end(constants.dir)
2203 - final_size.main(constants.dir)
2204 + resolved_margin.main_start(constants.dir)
2205 - resolved_margin.main_end(constants.dir))
2206 / 2.0
2207 }
2208 }
2209 };
2210
2211 let offset_cross = if let Some(start) = start_cross {
2214 start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2215 } else if let Some(end) = end_cross {
2216 constants.container_size.cross(constants.dir)
2217 - constants.border.cross_end(constants.dir)
2218 - constants.scrollbar_gutter.cross(constants.dir)
2219 - final_size.cross(constants.dir)
2220 - end
2221 - resolved_margin.cross_end(constants.dir)
2222 } else {
2223 match (align_self, constants.is_wrap_reverse) {
2224 (AlignSelf::Start, _)
2228 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
2229 | (AlignSelf::FlexEnd, true) => {
2230 constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2231 }
2232 (AlignSelf::End, _)
2233 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
2234 | (AlignSelf::FlexEnd, false) => {
2235 constants.container_size.cross(constants.dir)
2236 - constants.content_box_inset.cross_end(constants.dir)
2237 - final_size.cross(constants.dir)
2238 - resolved_margin.cross_end(constants.dir)
2239 }
2240 (AlignSelf::Center, _) => {
2241 (constants.container_size.cross(constants.dir)
2242 + constants.content_box_inset.cross_start(constants.dir)
2243 - constants.content_box_inset.cross_end(constants.dir)
2244 - final_size.cross(constants.dir)
2245 + resolved_margin.cross_start(constants.dir)
2246 - resolved_margin.cross_end(constants.dir))
2247 / 2.0
2248 }
2249 }
2250 };
2251
2252 let location = match constants.is_row {
2253 true => Point { x: offset_main, y: offset_cross },
2254 false => Point { x: offset_cross, y: offset_main },
2255 };
2256 let scrollbar_size = Size {
2257 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
2258 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
2259 };
2260 tree.set_unrounded_layout(
2261 child,
2262 &Layout {
2263 order: order as u32,
2264 size: final_size,
2265 #[cfg(feature = "content_size")]
2266 content_size: layout_output.content_size,
2267 scrollbar_size,
2268 location,
2269 padding,
2270 border,
2271 margin: resolved_margin,
2272 },
2273 );
2274
2275 #[cfg(feature = "content_size")]
2276 {
2277 let size_content_size_contribution = Size {
2278 width: match overflow.x {
2279 Overflow::Visible => f32_max(final_size.width, layout_output.content_size.width),
2280 _ => final_size.width,
2281 },
2282 height: match overflow.y {
2283 Overflow::Visible => f32_max(final_size.height, layout_output.content_size.height),
2284 _ => final_size.height,
2285 },
2286 };
2287 if size_content_size_contribution.has_non_zero_area() {
2288 let content_size_contribution = Size {
2289 width: location.x + size_content_size_contribution.width,
2290 height: location.y + size_content_size_contribution.height,
2291 };
2292 content_size = content_size.f32_max(content_size_contribution);
2293 }
2294 }
2295 }
2296
2297 content_size
2298}
2299
2300#[inline(always)]
2304fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
2305 if num_items <= 1 {
2307 0.0
2309 } else {
2310 gap * (num_items - 1) as f32
2312 }
2313}