1use crate::compute::common::alignment::compute_alignment_offset;
3use crate::geometry::{Line, Point, Rect, Size};
4use crate::style::{
5 AlignContent, AlignItems, AlignSelf, AvailableSpace, FlexWrap, JustifyContent, LengthPercentageAuto, Overflow,
6 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, |val, basis| tree.calc(val, basis));
175 let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
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, |val, basis| tree.calc(val, basis))
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, |val, basis| tree.calc(val, basis))
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, |val, basis| tree.calc(val, basis))
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, 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
288 .gap()
289 .main(constants.dir)
290 .maybe_resolve(inner_container_size, |val, basis| tree.calc(val, basis))
291 .unwrap_or(0.0);
292 constants.gap.set_main(constants.dir, new_gap);
293 }
294
295 debug_log!("resolve_flexible_lengths");
297 for line in &mut flex_lines {
298 resolve_flexible_lengths(line, &constants);
299 }
300
301 debug_log!("determine_hypothetical_cross_size");
305 for line in &mut flex_lines {
306 determine_hypothetical_cross_size(tree, line, &constants, available_space);
307 }
308
309 debug_log!("calculate_children_base_lines");
312 calculate_children_base_lines(tree, known_dimensions, available_space, &mut flex_lines, &constants);
313
314 debug_log!("calculate_cross_size");
316 calculate_cross_size(&mut flex_lines, known_dimensions, &constants);
317
318 debug_log!("handle_align_content_stretch");
320 handle_align_content_stretch(&mut flex_lines, known_dimensions, &constants);
321
322 debug_log!("determine_used_cross_size");
339 determine_used_cross_size(tree, &mut flex_lines, &constants);
340
341 debug_log!("distribute_remaining_free_space");
345 distribute_remaining_free_space(&mut flex_lines, &constants);
346
347 debug_log!("resolve_cross_axis_auto_margins");
351 resolve_cross_axis_auto_margins(&mut flex_lines, &constants);
352
353 debug_log!("determine_container_cross_size");
355 let total_line_cross_size = determine_container_cross_size(&flex_lines, known_dimensions, &mut constants);
356
357 if run_mode == RunMode::ComputeSize {
360 return LayoutOutput::from_outer_size(constants.container_size);
361 }
362
363 debug_log!("align_flex_lines_per_align_content");
365 align_flex_lines_per_align_content(&mut flex_lines, &constants, total_line_cross_size);
366
367 debug_log!("final_layout_pass");
369 let inflow_content_size = final_layout_pass(tree, &mut flex_lines, &constants);
370
371 debug_log!("perform_absolute_layout_on_absolute_children");
373 let absolute_content_size = perform_absolute_layout_on_absolute_children(tree, node, &constants);
374
375 debug_log!("hidden_layout");
376 let len = tree.child_count(node);
377 for order in 0..len {
378 let child = tree.get_child_id(node, order);
379 if tree.get_flexbox_child_style(child).box_generation_mode() == BoxGenerationMode::None {
380 tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
381 tree.perform_child_layout(
382 child,
383 Size::NONE,
384 Size::NONE,
385 Size::MAX_CONTENT,
386 SizingMode::InherentSize,
387 Line::FALSE,
388 );
389 }
390 }
391
392 let first_vertical_baseline = if flex_lines.is_empty() {
395 None
396 } else {
397 flex_lines[0]
398 .items
399 .iter()
400 .find(|item| constants.is_column || item.align_self == AlignSelf::Baseline)
401 .or_else(|| flex_lines[0].items.iter().next())
402 .map(|child| {
403 let offset_vertical = if constants.is_row { child.offset_cross } else { child.offset_main };
404 offset_vertical + child.baseline
405 })
406 };
407
408 LayoutOutput::from_sizes_and_baselines(
409 constants.container_size,
410 inflow_content_size.f32_max(absolute_content_size),
411 Point { x: None, y: first_vertical_baseline },
412 )
413}
414
415#[inline]
417fn compute_constants(
418 tree: &impl LayoutFlexboxContainer,
419 style: impl FlexboxContainerStyle,
420 known_dimensions: Size<Option<f32>>,
421 parent_size: Size<Option<f32>>,
422) -> AlgoConstants {
423 let dir = style.flex_direction();
424 let is_row = dir.is_row();
425 let is_column = dir.is_column();
426 let is_wrap = matches!(style.flex_wrap(), FlexWrap::Wrap | FlexWrap::WrapReverse);
427 let is_wrap_reverse = style.flex_wrap() == FlexWrap::WrapReverse;
428
429 let aspect_ratio = style.aspect_ratio();
430 let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
431 let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
432 let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
433 let padding_border_sum = padding.sum_axes() + border.sum_axes();
434 let box_sizing_adjustment =
435 if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
436
437 let align_items = style.align_items().unwrap_or(AlignItems::Stretch);
438 let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
439 let justify_content = style.justify_content();
440
441 let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
445 Overflow::Scroll => style.scrollbar_width(),
446 _ => 0.0,
447 });
448 let mut content_box_inset = padding + border;
450 content_box_inset.right += scrollbar_gutter.x;
451 content_box_inset.bottom += scrollbar_gutter.y;
452
453 let node_outer_size = known_dimensions;
454 let node_inner_size = node_outer_size.maybe_sub(content_box_inset.sum_axes());
455 let gap = style.gap().resolve_or_zero(node_inner_size.or(Size::zero()), |val, basis| tree.calc(val, basis));
456
457 let container_size = Size::zero();
458 let inner_container_size = Size::zero();
459
460 AlgoConstants {
461 dir,
462 is_row,
463 is_column,
464 is_wrap,
465 is_wrap_reverse,
466 min_size: style
467 .min_size()
468 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
469 .maybe_apply_aspect_ratio(aspect_ratio)
470 .maybe_add(box_sizing_adjustment),
471 max_size: style
472 .max_size()
473 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
474 .maybe_apply_aspect_ratio(aspect_ratio)
475 .maybe_add(box_sizing_adjustment),
476 margin,
477 border,
478 gap,
479 content_box_inset,
480 scrollbar_gutter,
481 align_items,
482 align_content,
483 justify_content,
484 node_outer_size,
485 node_inner_size,
486 container_size,
487 inner_container_size,
488 }
489}
490
491#[inline]
497fn generate_anonymous_flex_items(
498 tree: &impl LayoutFlexboxContainer,
499 node: NodeId,
500 constants: &AlgoConstants,
501) -> Vec<FlexItem> {
502 tree.child_ids(node)
503 .enumerate()
504 .map(|(index, child)| (index, child, tree.get_flexbox_child_style(child)))
505 .filter(|(_, _, style)| style.position() != Position::Absolute)
506 .filter(|(_, _, style)| style.box_generation_mode() != BoxGenerationMode::None)
507 .map(|(index, child, child_style)| {
508 let aspect_ratio = child_style.aspect_ratio();
509 let padding = child_style
510 .padding()
511 .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis));
512 let border = child_style
513 .border()
514 .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis));
515 let pb_sum = (padding + border).sum_axes();
516 let box_sizing_adjustment =
517 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
518 FlexItem {
519 node: child,
520 order: index as u32,
521 size: child_style
522 .size()
523 .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
524 .maybe_apply_aspect_ratio(aspect_ratio)
525 .maybe_add(box_sizing_adjustment),
526 min_size: child_style
527 .min_size()
528 .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
529 .maybe_apply_aspect_ratio(aspect_ratio)
530 .maybe_add(box_sizing_adjustment),
531 max_size: child_style
532 .max_size()
533 .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
534 .maybe_apply_aspect_ratio(aspect_ratio)
535 .maybe_add(box_sizing_adjustment),
536
537 inset: child_style
538 .inset()
539 .zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s, |val, basis| tree.calc(val, basis))),
540 margin: child_style
541 .margin()
542 .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
543 margin_is_auto: child_style.margin().map(LengthPercentageAuto::is_auto),
544 padding: child_style
545 .padding()
546 .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
547 border: child_style
548 .border()
549 .resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
550 align_self: child_style.align_self().unwrap_or(constants.align_items),
551 overflow: child_style.overflow(),
552 scrollbar_width: child_style.scrollbar_width(),
553 flex_grow: child_style.flex_grow(),
554 flex_shrink: child_style.flex_shrink(),
555 flex_basis: 0.0,
556 inner_flex_basis: 0.0,
557 violation: 0.0,
558 frozen: false,
559
560 resolved_minimum_main_size: 0.0,
561 hypothetical_inner_size: Size::zero(),
562 hypothetical_outer_size: Size::zero(),
563 target_size: Size::zero(),
564 outer_target_size: Size::zero(),
565 content_flex_fraction: 0.0,
566
567 baseline: 0.0,
568
569 offset_main: 0.0,
570 offset_cross: 0.0,
571 }
572 })
573 .collect()
574}
575
576#[inline]
587#[must_use]
588fn determine_available_space(
589 known_dimensions: Size<Option<f32>>,
590 outer_available_space: Size<AvailableSpace>,
591 constants: &AlgoConstants,
592) -> Size<AvailableSpace> {
593 let width = match known_dimensions.width {
595 Some(node_width) => AvailableSpace::Definite(node_width - constants.content_box_inset.horizontal_axis_sum()),
596 None => outer_available_space
597 .width
598 .maybe_sub(constants.margin.horizontal_axis_sum())
599 .maybe_sub(constants.content_box_inset.horizontal_axis_sum()),
600 };
601
602 let height = match known_dimensions.height {
603 Some(node_height) => AvailableSpace::Definite(node_height - constants.content_box_inset.vertical_axis_sum()),
604 None => outer_available_space
605 .height
606 .maybe_sub(constants.margin.vertical_axis_sum())
607 .maybe_sub(constants.content_box_inset.vertical_axis_sum()),
608 };
609
610 Size { width, height }
611}
612
613#[inline]
641fn determine_flex_base_size(
642 tree: &mut impl LayoutFlexboxContainer,
643 constants: &AlgoConstants,
644 available_space: Size<AvailableSpace>,
645 flex_items: &mut [FlexItem],
646) {
647 let dir = constants.dir;
648
649 for child in flex_items.iter_mut() {
650 let child_style = tree.get_flexbox_child_style(child.node);
651
652 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
654 let child_parent_size = Size::from_cross(dir, cross_axis_parent_size);
655
656 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
658 let child_min_cross = child.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
659 let child_max_cross = child.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
660
661 let cross_axis_available_space: AvailableSpace = match available_space.cross(dir) {
663 AvailableSpace::Definite(val) => AvailableSpace::Definite(
664 cross_axis_parent_size.unwrap_or(val).maybe_clamp(child_min_cross, child_max_cross),
665 ),
666 AvailableSpace::MinContent => match child_min_cross {
667 Some(min) => AvailableSpace::Definite(min),
668 None => AvailableSpace::MinContent,
669 },
670 AvailableSpace::MaxContent => match child_max_cross {
671 Some(max) => AvailableSpace::Definite(max),
672 None => AvailableSpace::MaxContent,
673 },
674 };
675
676 let child_known_dimensions = {
678 let mut ckd = child.size.with_main(dir, None);
679 if child.align_self == AlignSelf::Stretch
680 && !child.margin_is_auto.cross_start(constants.dir)
681 && !child.margin_is_auto.cross_end(constants.dir)
682 && ckd.cross(dir).is_none()
683 {
684 ckd.set_cross(
685 dir,
686 cross_axis_available_space.into_option().maybe_sub(child.margin.cross_axis_sum(dir)),
687 );
688 }
689 ckd
690 };
691
692 let container_width = constants.node_inner_size.main(dir);
693 let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox {
694 let padding = child_style.padding().resolve_or_zero(container_width, |val, basis| tree.calc(val, basis));
695 let border = child_style.border().resolve_or_zero(container_width, |val, basis| tree.calc(val, basis));
696 (padding + border).sum_axes()
697 } else {
698 Size::ZERO
699 }
700 .main(dir);
701 let flex_basis = child_style
702 .flex_basis()
703 .maybe_resolve(container_width, |val, basis| tree.calc(val, basis))
704 .maybe_add(box_sizing_adjustment);
705
706 drop(child_style);
707
708 child.flex_basis = 'flex_basis: {
709 let main_size = child.size.main(dir);
719 if let Some(flex_basis) = flex_basis.or(main_size) {
720 break 'flex_basis flex_basis;
721 };
722
723 let child_available_space = Size::MAX_CONTENT
748 .with_main(
749 dir,
750 if available_space.main(dir) == AvailableSpace::MinContent {
752 AvailableSpace::MinContent
753 } else {
754 AvailableSpace::MaxContent
755 },
756 )
757 .with_cross(dir, cross_axis_available_space);
758
759 debug_log!("COMPUTE CHILD BASE SIZE:");
760 break 'flex_basis tree.measure_child_size(
761 child.node,
762 child_known_dimensions,
763 child_parent_size,
764 child_available_space,
765 SizingMode::ContentSize,
766 dir.main_axis(),
767 Line::FALSE,
768 );
769 };
770
771 let padding_border_sum = child.padding.main_axis_sum(constants.dir) + child.border.main_axis_sum(constants.dir);
779 child.flex_basis = child.flex_basis.max(padding_border_sum);
780
781 child.inner_flex_basis =
785 child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
786
787 let padding_border_axes_sums = (child.padding + child.border).sum_axes().map(Some);
788
789 let style_min_main_size =
796 child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);
797
798 child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
799 let min_content_main_size = {
800 let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
801
802 debug_log!("COMPUTE CHILD MIN SIZE:");
803 tree.measure_child_size(
804 child.node,
805 child_known_dimensions,
806 child_parent_size,
807 child_available_space,
808 SizingMode::ContentSize,
809 dir.main_axis(),
810 Line::FALSE,
811 )
812 };
813
814 let clamped_min_content_size =
817 min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
818 clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
819 });
820
821 let hypothetical_inner_min_main =
822 child.resolved_minimum_main_size.maybe_max(padding_border_axes_sums.main(constants.dir));
823 let hypothetical_inner_size =
824 child.flex_basis.maybe_clamp(Some(hypothetical_inner_min_main), child.max_size.main(constants.dir));
825 let hypothetical_outer_size = hypothetical_inner_size + child.margin.main_axis_sum(constants.dir);
826
827 child.hypothetical_inner_size.set_main(constants.dir, hypothetical_inner_size);
828 child.hypothetical_outer_size.set_main(constants.dir, hypothetical_outer_size);
829 }
830}
831
832#[inline]
850fn collect_flex_lines<'a>(
851 constants: &AlgoConstants,
852 available_space: Size<AvailableSpace>,
853 flex_items: &'a mut Vec<FlexItem>,
854) -> Vec<FlexLine<'a>> {
855 if !constants.is_wrap {
856 let mut lines = new_vec_with_capacity(1);
857 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
858 lines
859 } else {
860 let main_axis_available_space = match constants.max_size.main(constants.dir) {
861 Some(max_size) => AvailableSpace::Definite(
862 available_space
863 .main(constants.dir)
864 .into_option()
865 .unwrap_or(max_size)
866 .maybe_max(constants.min_size.main(constants.dir)),
867 ),
868 None => available_space.main(constants.dir),
869 };
870
871 match main_axis_available_space {
872 AvailableSpace::MaxContent => {
875 let mut lines = new_vec_with_capacity(1);
876 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
877 lines
878 }
879 AvailableSpace::MinContent => {
882 let mut lines = new_vec_with_capacity(flex_items.len());
883 let mut items = &mut flex_items[..];
884 while !items.is_empty() {
885 let (line_items, rest) = items.split_at_mut(1);
886 lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
887 items = rest;
888 }
889 lines
890 }
891 AvailableSpace::Definite(main_axis_available_space) => {
892 let mut lines = new_vec_with_capacity(1);
893 let mut flex_items = &mut flex_items[..];
894 let main_axis_gap = constants.gap.main(constants.dir);
895
896 while !flex_items.is_empty() {
897 let mut line_length = 0.0;
900 let index = flex_items
901 .iter()
902 .enumerate()
903 .find(|&(idx, child)| {
904 let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
907 line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
908 line_length > main_axis_available_space && idx != 0
909 })
910 .map(|(idx, _)| idx)
911 .unwrap_or(flex_items.len());
912
913 let (items, rest) = flex_items.split_at_mut(index);
914 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
915 flex_items = rest;
916 }
917 lines
918 }
919 }
920 }
921}
922
923fn determine_container_main_size(
925 tree: &mut impl LayoutFlexboxContainer,
926 available_space: Size<AvailableSpace>,
927 lines: &mut [FlexLine<'_>],
928 constants: &mut AlgoConstants,
929) {
930 let dir = constants.dir;
931 let main_content_box_inset = constants.content_box_inset.main_axis_sum(constants.dir);
932
933 let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
934 match available_space.main(dir) {
935 AvailableSpace::Definite(main_axis_available_space) => {
936 let longest_line_length: f32 = lines
937 .iter()
938 .map(|line| {
939 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
940 let total_target_size = line
941 .items
942 .iter()
943 .map(|child| {
944 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
945 (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
946 + child.margin.main_axis_sum(constants.dir))
947 .max(padding_border_sum)
948 })
949 .sum::<f32>();
950 total_target_size + line_main_axis_gap
951 })
952 .max_by(|a, b| a.total_cmp(b))
953 .unwrap_or(0.0);
954 let size = longest_line_length + main_content_box_inset;
955 if lines.len() > 1 {
956 f32_max(size, main_axis_available_space)
957 } else {
958 size
959 }
960 }
961 AvailableSpace::MinContent if constants.is_wrap => {
962 let longest_line_length: f32 = lines
963 .iter()
964 .map(|line| {
965 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
966 let total_target_size = line
967 .items
968 .iter()
969 .map(|child| {
970 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
971 (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
972 + child.margin.main_axis_sum(constants.dir))
973 .max(padding_border_sum)
974 })
975 .sum::<f32>();
976 total_target_size + line_main_axis_gap
977 })
978 .max_by(|a, b| a.total_cmp(b))
979 .unwrap_or(0.0);
980 longest_line_length + main_content_box_inset
981 }
982 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
983 let mut main_size = 0.0;
987
988 for line in lines.iter_mut() {
989 for item in line.items.iter_mut() {
990 let style_min = item.min_size.main(constants.dir);
991 let style_preferred = item.size.main(constants.dir);
992 let style_max = item.max_size.main(constants.dir);
993
994 let clamping_basis = Some(item.flex_basis).maybe_max(style_preferred);
1002 let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
1003 let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);
1004
1005 let min_main_size = style_min
1006 .maybe_max(flex_basis_min)
1007 .or(flex_basis_min)
1008 .unwrap_or(item.resolved_minimum_main_size)
1009 .max(item.resolved_minimum_main_size);
1010 let max_main_size =
1011 style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);
1012
1013 let content_contribution = match (min_main_size, style_preferred, max_main_size) {
1014 (min, Some(pref), max) if max <= min || max <= pref => {
1017 pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
1018 }
1019 (min, _, max) if max <= min => min + item.margin.main_axis_sum(constants.dir),
1020
1021 _ if item.is_scroll_container() => {
1024 item.flex_basis + item.margin.main_axis_sum(constants.dir)
1025 }
1026 _ => {
1027 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
1029
1030 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
1032 let child_min_cross = item.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
1033 let child_max_cross = item.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
1034 let cross_axis_available_space: AvailableSpace = available_space
1035 .cross(dir)
1036 .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
1037 .maybe_clamp(child_min_cross, child_max_cross);
1038
1039 let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
1040
1041 let child_known_dimensions = {
1043 let mut ckd = item.size.with_main(dir, None);
1044 if item.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
1045 ckd.set_cross(
1046 dir,
1047 cross_axis_available_space
1048 .into_option()
1049 .maybe_sub(item.margin.cross_axis_sum(dir)),
1050 );
1051 }
1052 ckd
1053 };
1054
1055 debug_log!("COMPUTE CHILD BASE SIZE (for intrinsic main size):");
1058 let content_main_size = tree.measure_child_size(
1059 item.node,
1060 child_known_dimensions,
1061 constants.node_inner_size,
1062 child_available_space,
1063 SizingMode::InherentSize,
1064 dir.main_axis(),
1065 Line::FALSE,
1066 ) + item.margin.main_axis_sum(constants.dir);
1067
1068 if constants.is_row {
1081 content_main_size.maybe_clamp(style_min, style_max).max(main_content_box_inset)
1082 } else {
1083 content_main_size
1084 .max(item.flex_basis)
1085 .maybe_clamp(style_min, style_max)
1086 .max(main_content_box_inset)
1087 }
1088 }
1089 };
1090 item.content_flex_fraction = {
1091 let diff = content_contribution - item.flex_basis;
1092 if diff > 0.0 {
1093 diff / f32_max(1.0, item.flex_grow)
1094 } else if diff < 0.0 {
1095 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
1096 diff / scaled_shrink_factor
1097 } else {
1098 0.0
1100 }
1101 };
1102 }
1103
1104 let item_main_size_sum = line
1122 .items
1123 .iter_mut()
1124 .map(|item| {
1125 let flex_fraction = item.content_flex_fraction;
1126 let flex_contribution = if item.content_flex_fraction > 0.0 {
1129 f32_max(1.0, item.flex_grow) * flex_fraction
1130 } else if item.content_flex_fraction < 0.0 {
1131 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
1132 scaled_shrink_factor * flex_fraction
1133 } else {
1134 0.0
1135 };
1136 let size = item.flex_basis + flex_contribution;
1137 item.outer_target_size.set_main(constants.dir, size);
1138 item.target_size.set_main(constants.dir, size);
1139 size
1140 })
1141 .sum::<f32>();
1142
1143 let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1144 main_size = f32_max(main_size, item_main_size_sum + gap_sum)
1145 }
1146
1147 main_size + main_content_box_inset
1148 }
1149 }
1150 });
1151
1152 let outer_main_size = outer_main_size
1153 .maybe_clamp(constants.min_size.main(constants.dir), constants.max_size.main(constants.dir))
1154 .max(main_content_box_inset - constants.scrollbar_gutter.main(constants.dir));
1155
1156 let inner_main_size = f32_max(outer_main_size - main_content_box_inset, 0.0);
1158 constants.container_size.set_main(constants.dir, outer_main_size);
1159 constants.inner_container_size.set_main(constants.dir, inner_main_size);
1160 constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
1161}
1162
1163#[inline]
1168fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants) {
1169 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1170
1171 let total_hypothetical_outer_main_size =
1177 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
1178 let used_flex_factor: f32 = total_main_axis_gap + total_hypothetical_outer_main_size;
1179 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1180 let shrinking = used_flex_factor > constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1181 let exactly_sized = !growing & !shrinking;
1182
1183 for child in line.items.iter_mut() {
1191 let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
1192 child.target_size.set_main(constants.dir, inner_target_size);
1193
1194 if exactly_sized
1195 || (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
1196 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1197 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1198 {
1199 child.frozen = true;
1200 let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1201 child.outer_target_size.set_main(constants.dir, outer_target_size);
1202 }
1203 }
1204
1205 if exactly_sized {
1206 return;
1207 }
1208
1209 let used_space: f32 = total_main_axis_gap
1214 + line
1215 .items
1216 .iter()
1217 .map(|child| {
1218 if child.frozen {
1219 child.outer_target_size.main(constants.dir)
1220 } else {
1221 child.flex_basis + child.margin.main_axis_sum(constants.dir)
1222 }
1223 })
1224 .sum::<f32>();
1225
1226 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1227
1228 loop {
1231 if line.items.iter().all(|child| child.frozen) {
1235 break;
1236 }
1237
1238 let used_space: f32 = total_main_axis_gap
1245 + line
1246 .items
1247 .iter()
1248 .map(|child| {
1249 if child.frozen {
1250 child.outer_target_size.main(constants.dir)
1251 } else {
1252 child.flex_basis + child.margin.main_axis_sum(constants.dir)
1253 }
1254 })
1255 .sum::<f32>();
1256
1257 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1258
1259 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1260 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1261 (flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
1262 });
1263
1264 let free_space = if growing && sum_flex_grow < 1.0 {
1265 (initial_free_space * sum_flex_grow - total_main_axis_gap)
1266 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1267 } else if shrinking && sum_flex_shrink < 1.0 {
1268 (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1269 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1270 } else {
1271 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1272 .unwrap_or(used_flex_factor - used_space)
1273 };
1274
1275 if free_space.is_normal() {
1295 if growing && sum_flex_grow > 0.0 {
1296 for child in &mut unfrozen {
1297 child
1298 .target_size
1299 .set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
1300 }
1301 } else if shrinking && sum_flex_shrink > 0.0 {
1302 let sum_scaled_shrink_factor: f32 =
1303 unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
1304
1305 if sum_scaled_shrink_factor > 0.0 {
1306 for child in &mut unfrozen {
1307 let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
1308 child.target_size.set_main(
1309 constants.dir,
1310 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1311 )
1312 }
1313 }
1314 }
1315 }
1316
1317 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1323 let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
1324 let max_main = child.max_size.main(constants.dir);
1325 let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1326 child.violation = clamped - child.target_size.main(constants.dir);
1327 child.target_size.set_main(constants.dir, clamped);
1328 child.outer_target_size.set_main(
1329 constants.dir,
1330 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1331 );
1332
1333 acc + child.violation
1334 });
1335
1336 for child in &mut unfrozen {
1346 match total_violation {
1347 v if v > 0.0 => child.frozen = child.violation > 0.0,
1348 v if v < 0.0 => child.frozen = child.violation < 0.0,
1349 _ => child.frozen = true,
1350 }
1351 }
1352
1353 }
1355}
1356
1357#[inline]
1364fn determine_hypothetical_cross_size(
1365 tree: &mut impl LayoutFlexboxContainer,
1366 line: &mut FlexLine,
1367 constants: &AlgoConstants,
1368 available_space: Size<AvailableSpace>,
1369) {
1370 for child in line.items.iter_mut() {
1371 let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
1372
1373 let child_known_main = constants.container_size.main(constants.dir).into();
1374
1375 let child_cross = child
1376 .size
1377 .cross(constants.dir)
1378 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1379 .maybe_max(padding_border_sum);
1380
1381 let child_available_cross = available_space
1382 .cross(constants.dir)
1383 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1384 .maybe_max(padding_border_sum);
1385
1386 let child_inner_cross = child_cross.unwrap_or_else(|| {
1387 tree.measure_child_size(
1388 child.node,
1389 Size {
1390 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1391 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1392 },
1393 constants.node_inner_size,
1394 Size {
1395 width: if constants.is_row { child_known_main } else { child_available_cross },
1396 height: if constants.is_row { child_available_cross } else { child_known_main },
1397 },
1398 SizingMode::ContentSize,
1399 constants.dir.cross_axis(),
1400 Line::FALSE,
1401 )
1402 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1403 .max(padding_border_sum)
1404 });
1405 let child_outer_cross = child_inner_cross + child.margin.cross_axis_sum(constants.dir);
1406
1407 child.hypothetical_inner_size.set_cross(constants.dir, child_inner_cross);
1408 child.hypothetical_outer_size.set_cross(constants.dir, child_outer_cross);
1409 }
1410}
1411
1412#[inline]
1414fn calculate_children_base_lines(
1415 tree: &mut impl LayoutFlexboxContainer,
1416 node_size: Size<Option<f32>>,
1417 available_space: Size<AvailableSpace>,
1418 flex_lines: &mut [FlexLine],
1419 constants: &AlgoConstants,
1420) {
1421 if !constants.is_row {
1425 return;
1426 }
1427
1428 for line in flex_lines {
1429 let line_baseline_child_count =
1431 line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1432 if line_baseline_child_count <= 1 {
1433 continue;
1434 }
1435
1436 for child in line.items.iter_mut() {
1437 if child.align_self != AlignSelf::Baseline {
1439 continue;
1440 }
1441
1442 let measured_size_and_baselines = tree.perform_child_layout(
1443 child.node,
1444 Size {
1445 width: if constants.is_row {
1446 child.target_size.width.into()
1447 } else {
1448 child.hypothetical_inner_size.width.into()
1449 },
1450 height: if constants.is_row {
1451 child.hypothetical_inner_size.height.into()
1452 } else {
1453 child.target_size.height.into()
1454 },
1455 },
1456 constants.node_inner_size,
1457 Size {
1458 width: if constants.is_row {
1459 constants.container_size.width.into()
1460 } else {
1461 available_space.width.maybe_set(node_size.width)
1462 },
1463 height: if constants.is_row {
1464 available_space.height.maybe_set(node_size.height)
1465 } else {
1466 constants.container_size.height.into()
1467 },
1468 },
1469 SizingMode::ContentSize,
1470 Line::FALSE,
1471 );
1472
1473 let baseline = measured_size_and_baselines.first_baselines.y;
1474 let height = measured_size_and_baselines.size.height;
1475
1476 child.baseline = baseline.unwrap_or(height) + child.margin.top;
1477 }
1478 }
1479}
1480
1481#[inline]
1487fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1488 if !constants.is_wrap && node_size.cross(constants.dir).is_some() {
1491 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1492 let cross_min_size = constants.min_size.cross(constants.dir);
1493 let cross_max_size = constants.max_size.cross(constants.dir);
1494 flex_lines[0].cross_size = node_size
1495 .cross(constants.dir)
1496 .maybe_clamp(cross_min_size, cross_max_size)
1497 .maybe_sub(cross_axis_padding_border)
1498 .maybe_max(0.0)
1499 .unwrap_or(0.0);
1500 } else {
1501 for line in flex_lines.iter_mut() {
1515 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1516 line.cross_size = line
1517 .items
1518 .iter()
1519 .map(|child| {
1520 if child.align_self == AlignSelf::Baseline
1521 && !child.margin_is_auto.cross_start(constants.dir)
1522 && !child.margin_is_auto.cross_end(constants.dir)
1523 {
1524 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1525 } else {
1526 child.hypothetical_outer_size.cross(constants.dir)
1527 }
1528 })
1529 .fold(0.0, |acc, x| acc.max(x));
1530 }
1531
1532 if !constants.is_wrap {
1535 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1536 let cross_min_size = constants.min_size.cross(constants.dir);
1537 let cross_max_size = constants.max_size.cross(constants.dir);
1538 flex_lines[0].cross_size = flex_lines[0].cross_size.maybe_clamp(
1539 cross_min_size.maybe_sub(cross_axis_padding_border),
1540 cross_max_size.maybe_sub(cross_axis_padding_border),
1541 );
1542 }
1543 }
1544}
1545
1546#[inline]
1554fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1555 if constants.align_content == AlignContent::Stretch {
1556 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1557 let cross_min_size = constants.min_size.cross(constants.dir);
1558 let cross_max_size = constants.max_size.cross(constants.dir);
1559 let container_min_inner_cross = node_size
1560 .cross(constants.dir)
1561 .or(cross_min_size)
1562 .maybe_clamp(cross_min_size, cross_max_size)
1563 .maybe_sub(cross_axis_padding_border)
1564 .maybe_max(0.0)
1565 .unwrap_or(0.0);
1566
1567 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1568 let lines_total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1569
1570 if lines_total_cross < container_min_inner_cross {
1571 let remaining = container_min_inner_cross - lines_total_cross;
1572 let addition = remaining / flex_lines.len() as f32;
1573 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1574 }
1575 }
1576}
1577
1578#[inline]
1590fn determine_used_cross_size(
1591 tree: &impl LayoutFlexboxContainer,
1592 flex_lines: &mut [FlexLine],
1593 constants: &AlgoConstants,
1594) {
1595 for line in flex_lines {
1596 let line_cross_size = line.cross_size;
1597
1598 for child in line.items.iter_mut() {
1599 let child_style = tree.get_flexbox_child_style(child.node);
1600 child.target_size.set_cross(
1601 constants.dir,
1602 if child.align_self == AlignSelf::Stretch
1603 && !child.margin_is_auto.cross_start(constants.dir)
1604 && !child.margin_is_auto.cross_end(constants.dir)
1605 && child_style.size().cross(constants.dir).is_auto()
1606 {
1607 let padding = child_style
1611 .padding()
1612 .resolve_or_zero(constants.node_inner_size, |val, basis| tree.calc(val, basis));
1613 let border = child_style
1614 .border()
1615 .resolve_or_zero(constants.node_inner_size, |val, basis| tree.calc(val, basis));
1616 let pb_sum = (padding + border).sum_axes();
1617 let box_sizing_adjustment =
1618 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
1619
1620 let max_size_ignoring_aspect_ratio = child_style
1621 .max_size()
1622 .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
1623 .maybe_add(box_sizing_adjustment);
1624
1625 (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1626 child.min_size.cross(constants.dir),
1627 max_size_ignoring_aspect_ratio.cross(constants.dir),
1628 )
1629 } else {
1630 child.hypothetical_inner_size.cross(constants.dir)
1631 },
1632 );
1633
1634 child.outer_target_size.set_cross(
1635 constants.dir,
1636 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1637 );
1638 }
1639 }
1640}
1641
1642#[inline]
1653fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1654 for line in flex_lines {
1655 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1656 let used_space: f32 = total_main_axis_gap
1657 + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1658 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1659 let mut num_auto_margins = 0;
1660
1661 for child in line.items.iter_mut() {
1662 if child.margin_is_auto.main_start(constants.dir) {
1663 num_auto_margins += 1;
1664 }
1665 if child.margin_is_auto.main_end(constants.dir) {
1666 num_auto_margins += 1;
1667 }
1668 }
1669
1670 if free_space > 0.0 && num_auto_margins > 0 {
1671 let margin = free_space / num_auto_margins as f32;
1672
1673 for child in line.items.iter_mut() {
1674 if child.margin_is_auto.main_start(constants.dir) {
1675 if constants.is_row {
1676 child.margin.left = margin;
1677 } else {
1678 child.margin.top = margin;
1679 }
1680 }
1681 if child.margin_is_auto.main_end(constants.dir) {
1682 if constants.is_row {
1683 child.margin.right = margin;
1684 } else {
1685 child.margin.bottom = margin;
1686 }
1687 }
1688 }
1689 } else {
1690 let num_items = line.items.len();
1691 let layout_reverse = constants.dir.is_reverse();
1692 let gap = constants.gap.main(constants.dir);
1693 let is_safe = false; let raw_justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
1695 let justify_content_mode =
1696 apply_alignment_fallback(free_space, num_items, raw_justify_content_mode, is_safe);
1697
1698 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1699 child.offset_main =
1700 compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1701 };
1702
1703 if layout_reverse {
1704 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1705 } else {
1706 line.items.iter_mut().enumerate().for_each(justify_item);
1707 }
1708 }
1709 }
1710}
1711
1712#[inline]
1725fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1726 for line in flex_lines {
1727 let line_cross_size = line.cross_size;
1728 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1729
1730 for child in line.items.iter_mut() {
1731 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1732
1733 if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
1734 if constants.is_row {
1735 child.margin.top = free_space / 2.0;
1736 child.margin.bottom = free_space / 2.0;
1737 } else {
1738 child.margin.left = free_space / 2.0;
1739 child.margin.right = free_space / 2.0;
1740 }
1741 } else if child.margin_is_auto.cross_start(constants.dir) {
1742 if constants.is_row {
1743 child.margin.top = free_space;
1744 } else {
1745 child.margin.left = free_space;
1746 }
1747 } else if child.margin_is_auto.cross_end(constants.dir) {
1748 if constants.is_row {
1749 child.margin.bottom = free_space;
1750 } else {
1751 child.margin.right = free_space;
1752 }
1753 } else {
1754 child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1756 }
1757 }
1758 }
1759}
1760
1761#[inline]
1768fn align_flex_items_along_cross_axis(
1769 child: &FlexItem,
1770 free_space: f32,
1771 max_baseline: f32,
1772 constants: &AlgoConstants,
1773) -> f32 {
1774 match child.align_self {
1775 AlignSelf::Start => 0.0,
1776 AlignSelf::FlexStart => {
1777 if constants.is_wrap_reverse {
1778 free_space
1779 } else {
1780 0.0
1781 }
1782 }
1783 AlignSelf::End => free_space,
1784 AlignSelf::FlexEnd => {
1785 if constants.is_wrap_reverse {
1786 0.0
1787 } else {
1788 free_space
1789 }
1790 }
1791 AlignSelf::Center => free_space / 2.0,
1792 AlignSelf::Baseline => {
1793 if constants.is_row {
1794 max_baseline - child.baseline
1795 } else {
1796 if constants.is_wrap_reverse {
1799 free_space
1800 } else {
1801 0.0
1802 }
1803 }
1804 }
1805 AlignSelf::Stretch => {
1806 if constants.is_wrap_reverse {
1807 free_space
1808 } else {
1809 0.0
1810 }
1811 }
1812 }
1813}
1814
1815#[inline]
1825#[must_use]
1826fn determine_container_cross_size(
1827 flex_lines: &[FlexLine],
1828 node_size: Size<Option<f32>>,
1829 constants: &mut AlgoConstants,
1830) -> f32 {
1831 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1832 let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1833
1834 let padding_border_sum = constants.content_box_inset.cross_axis_sum(constants.dir);
1835 let cross_scrollbar_gutter = constants.scrollbar_gutter.cross(constants.dir);
1836 let min_cross_size = constants.min_size.cross(constants.dir);
1837 let max_cross_size = constants.max_size.cross(constants.dir);
1838 let outer_container_size = node_size
1839 .cross(constants.dir)
1840 .unwrap_or(total_line_cross_size + total_cross_axis_gap + padding_border_sum)
1841 .maybe_clamp(min_cross_size, max_cross_size)
1842 .max(padding_border_sum - cross_scrollbar_gutter);
1843 let inner_container_size = f32_max(outer_container_size - padding_border_sum, 0.0);
1844
1845 constants.container_size.set_cross(constants.dir, outer_container_size);
1846 constants.inner_container_size.set_cross(constants.dir, inner_container_size);
1847
1848 total_line_cross_size
1849}
1850
1851#[inline]
1857fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
1858 let num_lines = flex_lines.len();
1859 let gap = constants.gap.cross(constants.dir);
1860 let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1861 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1862 let is_safe = false; let align_content_mode = apply_alignment_fallback(free_space, num_lines, constants.align_content, is_safe);
1865
1866 let align_line = |(i, line): (usize, &mut FlexLine)| {
1867 line.offset_cross =
1868 compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1869 };
1870
1871 if constants.is_wrap_reverse {
1872 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1873 } else {
1874 flex_lines.iter_mut().enumerate().for_each(align_line);
1875 }
1876}
1877
1878#[allow(clippy::too_many_arguments)]
1880fn calculate_flex_item(
1881 tree: &mut impl LayoutFlexboxContainer,
1882 item: &mut FlexItem,
1883 total_offset_main: &mut f32,
1884 total_offset_cross: f32,
1885 line_offset_cross: f32,
1886 #[cfg(feature = "content_size")] total_content_size: &mut Size<f32>,
1887 container_size: Size<f32>,
1888 node_inner_size: Size<Option<f32>>,
1889 direction: FlexDirection,
1890) {
1891 let layout_output = tree.perform_child_layout(
1892 item.node,
1893 item.target_size.map(|s| s.into()),
1894 node_inner_size,
1895 container_size.map(|s| s.into()),
1896 SizingMode::ContentSize,
1897 Line::FALSE,
1898 );
1899 let LayoutOutput {
1900 size,
1901 #[cfg(feature = "content_size")]
1902 content_size,
1903 ..
1904 } = layout_output;
1905
1906 let offset_main = *total_offset_main
1907 + item.offset_main
1908 + item.margin.main_start(direction)
1909 + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1910
1911 let offset_cross = total_offset_cross
1912 + item.offset_cross
1913 + line_offset_cross
1914 + item.margin.cross_start(direction)
1915 + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1916
1917 if direction.is_row() {
1918 let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1919 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1920 item.baseline = baseline_offset_cross + inner_baseline;
1921 } else {
1922 let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1923 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1924 item.baseline = baseline_offset_main + inner_baseline;
1925 }
1926
1927 let location = match direction.is_row() {
1928 true => Point { x: offset_main, y: offset_cross },
1929 false => Point { x: offset_cross, y: offset_main },
1930 };
1931 let scrollbar_size = Size {
1932 width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1933 height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1934 };
1935
1936 tree.set_unrounded_layout(
1937 item.node,
1938 &Layout {
1939 order: item.order,
1940 size,
1941 #[cfg(feature = "content_size")]
1942 content_size,
1943 scrollbar_size,
1944 location,
1945 padding: item.padding,
1946 border: item.border,
1947 margin: item.margin,
1948 },
1949 );
1950
1951 *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
1952
1953 #[cfg(feature = "content_size")]
1954 {
1955 *total_content_size =
1956 total_content_size.f32_max(compute_content_size_contribution(location, size, content_size, item.overflow));
1957 }
1958}
1959
1960#[allow(clippy::too_many_arguments)]
1962fn calculate_layout_line(
1963 tree: &mut impl LayoutFlexboxContainer,
1964 line: &mut FlexLine,
1965 total_offset_cross: &mut f32,
1966 #[cfg(feature = "content_size")] content_size: &mut Size<f32>,
1967 container_size: Size<f32>,
1968 node_inner_size: Size<Option<f32>>,
1969 padding_border: Rect<f32>,
1970 direction: FlexDirection,
1971) {
1972 let mut total_offset_main = padding_border.main_start(direction);
1973 let line_offset_cross = line.offset_cross;
1974
1975 if direction.is_reverse() {
1976 for item in line.items.iter_mut().rev() {
1977 calculate_flex_item(
1978 tree,
1979 item,
1980 &mut total_offset_main,
1981 *total_offset_cross,
1982 line_offset_cross,
1983 #[cfg(feature = "content_size")]
1984 content_size,
1985 container_size,
1986 node_inner_size,
1987 direction,
1988 );
1989 }
1990 } else {
1991 for item in line.items.iter_mut() {
1992 calculate_flex_item(
1993 tree,
1994 item,
1995 &mut total_offset_main,
1996 *total_offset_cross,
1997 line_offset_cross,
1998 #[cfg(feature = "content_size")]
1999 content_size,
2000 container_size,
2001 node_inner_size,
2002 direction,
2003 );
2004 }
2005 }
2006
2007 *total_offset_cross += line_offset_cross + line.cross_size;
2008}
2009
2010#[inline]
2012fn final_layout_pass(
2013 tree: &mut impl LayoutFlexboxContainer,
2014 flex_lines: &mut [FlexLine],
2015 constants: &AlgoConstants,
2016) -> Size<f32> {
2017 let mut total_offset_cross = constants.content_box_inset.cross_start(constants.dir);
2018
2019 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
2020 let mut content_size = Size::ZERO;
2021
2022 if constants.is_wrap_reverse {
2023 for line in flex_lines.iter_mut().rev() {
2024 calculate_layout_line(
2025 tree,
2026 line,
2027 &mut total_offset_cross,
2028 #[cfg(feature = "content_size")]
2029 &mut content_size,
2030 constants.container_size,
2031 constants.node_inner_size,
2032 constants.content_box_inset,
2033 constants.dir,
2034 );
2035 }
2036 } else {
2037 for line in flex_lines.iter_mut() {
2038 calculate_layout_line(
2039 tree,
2040 line,
2041 &mut total_offset_cross,
2042 #[cfg(feature = "content_size")]
2043 &mut content_size,
2044 constants.container_size,
2045 constants.node_inner_size,
2046 constants.content_box_inset,
2047 constants.dir,
2048 );
2049 }
2050 }
2051
2052 content_size.width += constants.content_box_inset.right - constants.border.right - constants.scrollbar_gutter.x;
2053 content_size.height += constants.content_box_inset.bottom - constants.border.bottom - constants.scrollbar_gutter.y;
2054
2055 content_size
2056}
2057
2058#[inline]
2060fn perform_absolute_layout_on_absolute_children(
2061 tree: &mut impl LayoutFlexboxContainer,
2062 node: NodeId,
2063 constants: &AlgoConstants,
2064) -> Size<f32> {
2065 let container_width = constants.container_size.width;
2066 let container_height = constants.container_size.height;
2067 let inset_relative_size =
2068 constants.container_size - constants.border.sum_axes() - constants.scrollbar_gutter.into();
2069
2070 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
2071 let mut content_size = Size::ZERO;
2072
2073 for order in 0..tree.child_count(node) {
2074 let child = tree.get_child_id(node, order);
2075 let child_style = tree.get_flexbox_child_style(child);
2076
2077 if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
2079 {
2080 continue;
2081 }
2082
2083 let overflow = child_style.overflow();
2084 let scrollbar_width = child_style.scrollbar_width();
2085 let aspect_ratio = child_style.aspect_ratio();
2086 let align_self = child_style.align_self().unwrap_or(constants.align_items);
2087 let margin = child_style
2088 .margin()
2089 .map(|margin| margin.resolve_to_option(inset_relative_size.width, |val, basis| tree.calc(val, basis)));
2090 let padding =
2091 child_style.padding().resolve_or_zero(Some(inset_relative_size.width), |val, basis| tree.calc(val, basis));
2092 let border =
2093 child_style.border().resolve_or_zero(Some(inset_relative_size.width), |val, basis| tree.calc(val, basis));
2094 let padding_border_sum = (padding + border).sum_axes();
2095 let box_sizing_adjustment =
2096 if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
2097
2098 let left =
2101 child_style.inset().left.maybe_resolve(inset_relative_size.width, |val, basis| tree.calc(val, basis));
2102 let right =
2103 child_style.inset().right.maybe_resolve(inset_relative_size.width, |val, basis| tree.calc(val, basis));
2104 let top = child_style.inset().top.maybe_resolve(inset_relative_size.height, |val, basis| tree.calc(val, basis));
2105 let bottom =
2106 child_style.inset().bottom.maybe_resolve(inset_relative_size.height, |val, basis| tree.calc(val, basis));
2107
2108 let style_size = child_style
2110 .size()
2111 .maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
2112 .maybe_apply_aspect_ratio(aspect_ratio)
2113 .maybe_add(box_sizing_adjustment);
2114 let min_size = child_style
2115 .min_size()
2116 .maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
2117 .maybe_apply_aspect_ratio(aspect_ratio)
2118 .maybe_add(box_sizing_adjustment)
2119 .or(padding_border_sum.map(Some))
2120 .maybe_max(padding_border_sum);
2121 let max_size = child_style
2122 .max_size()
2123 .maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
2124 .maybe_apply_aspect_ratio(aspect_ratio)
2125 .maybe_add(box_sizing_adjustment);
2126 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
2127
2128 drop(child_style);
2129
2130 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
2134 let new_width_raw = inset_relative_size.width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
2135 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
2136 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2137 }
2138
2139 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
2143 let new_height_raw =
2144 inset_relative_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
2145 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
2146 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2147 }
2148 let layout_output = tree.perform_child_layout(
2149 child,
2150 known_dimensions,
2151 constants.node_inner_size,
2152 Size {
2153 width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
2154 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
2155 },
2156 SizingMode::InherentSize,
2157 Line::FALSE,
2158 );
2159 let measured_size = layout_output.size;
2160 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
2161
2162 let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
2163
2164 let free_space = Size {
2165 width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
2166 height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
2167 }
2168 .f32_max(Size::ZERO);
2169
2170 let resolved_margin = {
2172 let auto_margin_size = Size {
2173 width: {
2174 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
2175 if auto_margin_count > 0 {
2176 free_space.width / auto_margin_count as f32
2177 } else {
2178 0.0
2179 }
2180 },
2181 height: {
2182 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
2183 if auto_margin_count > 0 {
2184 free_space.height / auto_margin_count as f32
2185 } else {
2186 0.0
2187 }
2188 },
2189 };
2190
2191 Rect {
2192 left: margin.left.unwrap_or(auto_margin_size.width),
2193 right: margin.right.unwrap_or(auto_margin_size.width),
2194 top: margin.top.unwrap_or(auto_margin_size.height),
2195 bottom: margin.bottom.unwrap_or(auto_margin_size.height),
2196 }
2197 };
2198
2199 let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
2201 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
2202
2203 let offset_main = if let Some(start) = start_main {
2206 start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2207 } else if let Some(end) = end_main {
2208 constants.container_size.main(constants.dir)
2209 - constants.border.main_end(constants.dir)
2210 - constants.scrollbar_gutter.main(constants.dir)
2211 - final_size.main(constants.dir)
2212 - end
2213 - resolved_margin.main_end(constants.dir)
2214 } else {
2215 match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
2218 (JustifyContent::SpaceBetween, _)
2219 | (JustifyContent::Start, _)
2220 | (JustifyContent::Stretch, false)
2221 | (JustifyContent::FlexStart, false)
2222 | (JustifyContent::FlexEnd, true) => {
2223 constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2224 }
2225 (JustifyContent::End, _)
2226 | (JustifyContent::FlexEnd, false)
2227 | (JustifyContent::FlexStart, true)
2228 | (JustifyContent::Stretch, true) => {
2229 constants.container_size.main(constants.dir)
2230 - constants.content_box_inset.main_end(constants.dir)
2231 - final_size.main(constants.dir)
2232 - resolved_margin.main_end(constants.dir)
2233 }
2234 (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
2235 (constants.container_size.main(constants.dir)
2236 + constants.content_box_inset.main_start(constants.dir)
2237 - constants.content_box_inset.main_end(constants.dir)
2238 - final_size.main(constants.dir)
2239 + resolved_margin.main_start(constants.dir)
2240 - resolved_margin.main_end(constants.dir))
2241 / 2.0
2242 }
2243 }
2244 };
2245
2246 let offset_cross = if let Some(start) = start_cross {
2249 start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2250 } else if let Some(end) = end_cross {
2251 constants.container_size.cross(constants.dir)
2252 - constants.border.cross_end(constants.dir)
2253 - constants.scrollbar_gutter.cross(constants.dir)
2254 - final_size.cross(constants.dir)
2255 - end
2256 - resolved_margin.cross_end(constants.dir)
2257 } else {
2258 match (align_self, constants.is_wrap_reverse) {
2259 (AlignSelf::Start, _)
2263 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
2264 | (AlignSelf::FlexEnd, true) => {
2265 constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2266 }
2267 (AlignSelf::End, _)
2268 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
2269 | (AlignSelf::FlexEnd, false) => {
2270 constants.container_size.cross(constants.dir)
2271 - constants.content_box_inset.cross_end(constants.dir)
2272 - final_size.cross(constants.dir)
2273 - resolved_margin.cross_end(constants.dir)
2274 }
2275 (AlignSelf::Center, _) => {
2276 (constants.container_size.cross(constants.dir)
2277 + constants.content_box_inset.cross_start(constants.dir)
2278 - constants.content_box_inset.cross_end(constants.dir)
2279 - final_size.cross(constants.dir)
2280 + resolved_margin.cross_start(constants.dir)
2281 - resolved_margin.cross_end(constants.dir))
2282 / 2.0
2283 }
2284 }
2285 };
2286
2287 let location = match constants.is_row {
2288 true => Point { x: offset_main, y: offset_cross },
2289 false => Point { x: offset_cross, y: offset_main },
2290 };
2291 let scrollbar_size = Size {
2292 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
2293 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
2294 };
2295 tree.set_unrounded_layout(
2296 child,
2297 &Layout {
2298 order: order as u32,
2299 size: final_size,
2300 #[cfg(feature = "content_size")]
2301 content_size: layout_output.content_size,
2302 scrollbar_size,
2303 location,
2304 padding,
2305 border,
2306 margin: resolved_margin,
2307 },
2308 );
2309
2310 #[cfg(feature = "content_size")]
2311 {
2312 let size_content_size_contribution = Size {
2313 width: match overflow.x {
2314 Overflow::Visible => f32_max(final_size.width, layout_output.content_size.width),
2315 _ => final_size.width,
2316 },
2317 height: match overflow.y {
2318 Overflow::Visible => f32_max(final_size.height, layout_output.content_size.height),
2319 _ => final_size.height,
2320 },
2321 };
2322 if size_content_size_contribution.has_non_zero_area() {
2323 let content_size_contribution = Size {
2324 width: location.x + size_content_size_contribution.width,
2325 height: location.y + size_content_size_contribution.height,
2326 };
2327 content_size = content_size.f32_max(content_size_contribution);
2328 }
2329 }
2330 }
2331
2332 content_size
2333}
2334
2335#[inline(always)]
2339fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
2340 if num_items <= 1 {
2342 0.0
2344 } else {
2345 gap * (num_items - 1) as f32
2347 }
2348}