1use crate::compute::common::alignment::compute_alignment_offset;
3use crate::geometry::{Line, Point, Rect, Size};
4use crate::style::{
5 AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
6 LengthPercentageAuto, Overflow, Position,
7};
8use crate::style::{FlexDirection, Style};
9use crate::style_helpers::{TaffyMaxContent, TaffyMinContent};
10use crate::tree::{Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
11use crate::tree::{NodeId, PartialLayoutTree, PartialLayoutTreeExt};
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};
16
17#[cfg(feature = "content_size")]
18use super::common::content_size::compute_content_size_contribution;
19
20struct FlexItem {
22 node: NodeId,
24
25 order: u32,
27
28 size: Size<Option<f32>>,
30 min_size: Size<Option<f32>>,
32 max_size: Size<Option<f32>>,
34 align_self: AlignSelf,
36
37 overflow: Point<Overflow>,
39 scrollbar_width: f32,
41 flex_shrink: f32,
43 flex_grow: f32,
45
46 resolved_minimum_main_size: f32,
49
50 inset: Rect<Option<f32>>,
52 margin: Rect<f32>,
54 margin_is_auto: Rect<bool>,
56 padding: Rect<f32>,
58 border: Rect<f32>,
60
61 flex_basis: f32,
63 inner_flex_basis: f32,
65 violation: f32,
67 frozen: bool,
69
70 content_flex_fraction: f32,
73
74 hypothetical_inner_size: Size<f32>,
76 hypothetical_outer_size: Size<f32>,
78 target_size: Size<f32>,
80 outer_target_size: Size<f32>,
82
83 baseline: f32,
85
86 offset_main: f32,
91 offset_cross: f32,
96}
97
98struct FlexLine<'a> {
100 items: &'a mut [FlexItem],
102 cross_size: f32,
104 offset_cross: f32,
106}
107
108struct AlgoConstants {
110 dir: FlexDirection,
112 is_row: bool,
114 is_column: bool,
116 is_wrap: bool,
118 is_wrap_reverse: bool,
120
121 min_size: Size<Option<f32>>,
123 max_size: Size<Option<f32>>,
125 margin: Rect<f32>,
127 border: Rect<f32>,
129 content_box_inset: Rect<f32>,
132 scrollbar_gutter: Point<f32>,
134 gap: Size<f32>,
136 align_items: AlignItems,
138 align_content: AlignContent,
140 justify_content: Option<JustifyContent>,
142
143 node_outer_size: Size<Option<f32>>,
145 node_inner_size: Size<Option<f32>>,
147
148 container_size: Size<f32>,
150 inner_container_size: Size<f32>,
152}
153
154pub fn compute_flexbox_layout(tree: &mut impl PartialLayoutTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
156 let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
157 let style = tree.get_style(node);
158
159 let aspect_ratio = style.aspect_ratio;
161 let min_size = style.min_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
162 let max_size = style.max_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
163 let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
164 style.size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size)
165 } else {
166 Size::NONE
167 };
168
169 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
171 (Some(min), Some(max)) if max <= min => Some(min),
172 _ => None,
173 });
174 let styled_based_known_dimensions = known_dimensions.or(min_max_definite_size).or(clamped_style_size);
175
176 if run_mode == RunMode::ComputeSize {
179 if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
180 return LayoutOutput::from_outer_size(Size { width, height });
181 }
182 }
183
184 debug_log!("FLEX: single-pass");
185 compute_preliminary(tree, node, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
186}
187
188fn compute_preliminary(tree: &mut impl PartialLayoutTree, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
190 let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
191
192 let mut constants = compute_constants(tree.get_style(node), known_dimensions, parent_size);
194
195 debug_log!("generate_anonymous_flex_items");
201 let mut flex_items = generate_anonymous_flex_items(tree, node, &constants);
202
203 debug_log!("determine_available_space");
207 let available_space = determine_available_space(known_dimensions, available_space, &constants);
208
209 debug_log!("determine_flex_base_size");
211 determine_flex_base_size(tree, &constants, available_space, &mut flex_items);
212
213 #[cfg(feature = "debug")]
214 for item in flex_items.iter() {
215 debug_log!("item.flex_basis", item.flex_basis);
216 debug_log!("item.inner_flex_basis", item.inner_flex_basis);
217 debug_log!("item.hypothetical_outer_size", dbg:item.hypothetical_outer_size);
218 debug_log!("item.hypothetical_inner_size", dbg:item.hypothetical_inner_size);
219 debug_log!("item.resolved_minimum_main_size", dbg:item.resolved_minimum_main_size);
220 }
221
222 debug_log!("collect_flex_lines");
229 let mut flex_lines = collect_flex_lines(&constants, available_space, &mut flex_items);
230
231 debug_log!("determine_container_main_size");
234 let original_gap = constants.gap;
235 if let Some(inner_main_size) = constants.node_inner_size.main(constants.dir) {
236 let outer_main_size = inner_main_size + constants.content_box_inset.main_axis_sum(constants.dir);
237 constants.inner_container_size.set_main(constants.dir, inner_main_size);
238 constants.container_size.set_main(constants.dir, outer_main_size);
239 } else {
240 determine_container_main_size(tree, available_space, &mut flex_lines, &mut constants);
242 constants.node_inner_size.set_main(constants.dir, Some(constants.inner_container_size.main(constants.dir)));
243 constants.node_outer_size.set_main(constants.dir, Some(constants.container_size.main(constants.dir)));
244
245 debug_log!("constants.node_outer_size", dbg:constants.node_outer_size);
246 debug_log!("constants.node_inner_size", dbg:constants.node_inner_size);
247
248 let style = tree.get_style(node);
250 let inner_container_size = constants.inner_container_size.main(constants.dir);
251 let new_gap = style.gap.main(constants.dir).maybe_resolve(inner_container_size).unwrap_or(0.0);
252 constants.gap.set_main(constants.dir, new_gap);
253 }
254
255 debug_log!("resolve_flexible_lengths");
257 for line in &mut flex_lines {
258 resolve_flexible_lengths(line, &constants, original_gap);
259 }
260
261 debug_log!("determine_hypothetical_cross_size");
265 for line in &mut flex_lines {
266 determine_hypothetical_cross_size(tree, line, &constants, available_space);
267 }
268
269 debug_log!("calculate_children_base_lines");
272 calculate_children_base_lines(tree, known_dimensions, available_space, &mut flex_lines, &constants);
273
274 debug_log!("calculate_cross_size");
276 calculate_cross_size(&mut flex_lines, known_dimensions, &constants);
277
278 debug_log!("handle_align_content_stretch");
280 handle_align_content_stretch(&mut flex_lines, known_dimensions, &constants);
281
282 debug_log!("determine_used_cross_size");
299 determine_used_cross_size(tree, &mut flex_lines, &constants);
300
301 debug_log!("distribute_remaining_free_space");
305 distribute_remaining_free_space(&mut flex_lines, &constants);
306
307 debug_log!("resolve_cross_axis_auto_margins");
311 resolve_cross_axis_auto_margins(&mut flex_lines, &constants);
312
313 debug_log!("determine_container_cross_size");
315 let total_line_cross_size = determine_container_cross_size(&flex_lines, known_dimensions, &mut constants);
316
317 if run_mode == RunMode::ComputeSize {
320 return LayoutOutput::from_outer_size(constants.container_size);
321 }
322
323 debug_log!("align_flex_lines_per_align_content");
325 align_flex_lines_per_align_content(&mut flex_lines, &constants, total_line_cross_size);
326
327 debug_log!("final_layout_pass");
329 let inflow_content_size = final_layout_pass(tree, &mut flex_lines, &constants);
330
331 debug_log!("perform_absolute_layout_on_absolute_children");
333 let absolute_content_size = perform_absolute_layout_on_absolute_children(tree, node, &constants);
334
335 debug_log!("hidden_layout");
336 let len = tree.child_count(node);
337 for order in 0..len {
338 let child = tree.get_child_id(node, order);
339 if tree.get_style(child).display == Display::None {
340 tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
341 tree.perform_child_layout(
342 child,
343 Size::NONE,
344 Size::NONE,
345 Size::MAX_CONTENT,
346 SizingMode::InherentSize,
347 Line::FALSE,
348 );
349 }
350 }
351
352 let first_vertical_baseline = if flex_lines.is_empty() {
355 None
356 } else {
357 flex_lines[0]
358 .items
359 .iter()
360 .find(|item| constants.is_column || item.align_self == AlignSelf::Baseline)
361 .or_else(|| flex_lines[0].items.iter().next())
362 .map(|child| {
363 let offset_vertical = if constants.is_row { child.offset_cross } else { child.offset_main };
364 offset_vertical + child.baseline
365 })
366 };
367
368 LayoutOutput::from_sizes_and_baselines(
369 constants.container_size,
370 inflow_content_size.f32_max(absolute_content_size),
371 Point { x: None, y: first_vertical_baseline },
372 )
373}
374
375#[inline]
377fn compute_constants(
378 style: &Style,
379 known_dimensions: Size<Option<f32>>,
380 parent_size: Size<Option<f32>>,
381) -> AlgoConstants {
382 let dir = style.flex_direction;
383 let is_row = dir.is_row();
384 let is_column = dir.is_column();
385 let is_wrap = matches!(style.flex_wrap, FlexWrap::Wrap | FlexWrap::WrapReverse);
386 let is_wrap_reverse = style.flex_wrap == FlexWrap::WrapReverse;
387
388 let aspect_ratio = style.aspect_ratio;
389 let margin = style.margin.resolve_or_zero(parent_size.width);
390 let padding = style.padding.resolve_or_zero(parent_size.width);
391 let border = style.border.resolve_or_zero(parent_size.width);
392 let align_items = style.align_items.unwrap_or(AlignItems::Stretch);
393 let align_content = style.align_content.unwrap_or(AlignContent::Stretch);
394 let justify_content = style.justify_content;
395
396 let scrollbar_gutter = style.overflow.transpose().map(|overflow| match overflow {
400 Overflow::Scroll => style.scrollbar_width,
401 _ => 0.0,
402 });
403 let mut content_box_inset = padding + border;
405 content_box_inset.right += scrollbar_gutter.x;
406 content_box_inset.bottom += scrollbar_gutter.y;
407
408 let node_outer_size = known_dimensions;
409 let node_inner_size = node_outer_size.maybe_sub(content_box_inset.sum_axes());
410 let gap = style.gap.resolve_or_zero(node_inner_size.or(Size::zero()));
411
412 let container_size = Size::zero();
413 let inner_container_size = Size::zero();
414
415 AlgoConstants {
416 dir,
417 is_row,
418 is_column,
419 is_wrap,
420 is_wrap_reverse,
421 min_size: style.min_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio),
422 max_size: style.max_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio),
423 margin,
424 border,
425 gap,
426 content_box_inset,
427 scrollbar_gutter,
428 align_items,
429 align_content,
430 justify_content,
431 node_outer_size,
432 node_inner_size,
433 container_size,
434 inner_container_size,
435 }
436}
437
438#[inline]
444fn generate_anonymous_flex_items(
445 tree: &impl PartialLayoutTree,
446 node: NodeId,
447 constants: &AlgoConstants,
448) -> Vec<FlexItem> {
449 tree.child_ids(node)
450 .enumerate()
451 .map(|(index, child)| (index, child, tree.get_style(child)))
452 .filter(|(_, _, style)| style.position != Position::Absolute)
453 .filter(|(_, _, style)| style.display != Display::None)
454 .map(|(index, child, child_style)| {
455 let aspect_ratio = child_style.aspect_ratio;
456 FlexItem {
457 node: child,
458 order: index as u32,
459 size: child_style.size.maybe_resolve(constants.node_inner_size).maybe_apply_aspect_ratio(aspect_ratio),
460 min_size: child_style
461 .min_size
462 .maybe_resolve(constants.node_inner_size)
463 .maybe_apply_aspect_ratio(aspect_ratio),
464 max_size: child_style
465 .max_size
466 .maybe_resolve(constants.node_inner_size)
467 .maybe_apply_aspect_ratio(aspect_ratio),
468
469 inset: child_style.inset.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
470 margin: child_style.margin.resolve_or_zero(constants.node_inner_size.width),
471 margin_is_auto: child_style.margin.map(|m| m == LengthPercentageAuto::Auto),
472 padding: child_style.padding.resolve_or_zero(constants.node_inner_size.width),
473 border: child_style.border.resolve_or_zero(constants.node_inner_size.width),
474 align_self: child_style.align_self.unwrap_or(constants.align_items),
475 overflow: child_style.overflow,
476 scrollbar_width: child_style.scrollbar_width,
477 flex_grow: child_style.flex_grow,
478 flex_shrink: child_style.flex_shrink,
479 flex_basis: 0.0,
480 inner_flex_basis: 0.0,
481 violation: 0.0,
482 frozen: false,
483
484 resolved_minimum_main_size: 0.0,
485 hypothetical_inner_size: Size::zero(),
486 hypothetical_outer_size: Size::zero(),
487 target_size: Size::zero(),
488 outer_target_size: Size::zero(),
489 content_flex_fraction: 0.0,
490
491 baseline: 0.0,
492
493 offset_main: 0.0,
494 offset_cross: 0.0,
495 }
496 })
497 .collect()
498}
499
500#[inline]
510#[must_use]
511fn determine_available_space(
512 known_dimensions: Size<Option<f32>>,
513 outer_available_space: Size<AvailableSpace>,
514 constants: &AlgoConstants,
515) -> Size<AvailableSpace> {
516 let width = match known_dimensions.width {
518 Some(node_width) => AvailableSpace::Definite(node_width - constants.content_box_inset.horizontal_axis_sum()),
519 None => outer_available_space
520 .width
521 .maybe_sub(constants.margin.horizontal_axis_sum())
522 .maybe_sub(constants.content_box_inset.horizontal_axis_sum()),
523 };
524
525 let height = match known_dimensions.height {
526 Some(node_height) => AvailableSpace::Definite(node_height - constants.content_box_inset.vertical_axis_sum()),
527 None => outer_available_space
528 .height
529 .maybe_sub(constants.margin.vertical_axis_sum())
530 .maybe_sub(constants.content_box_inset.vertical_axis_sum()),
531 };
532
533 Size { width, height }
534}
535
536#[inline]
564fn determine_flex_base_size(
565 tree: &mut impl PartialLayoutTree,
566 constants: &AlgoConstants,
567 available_space: Size<AvailableSpace>,
568 flex_items: &mut [FlexItem],
569) {
570 let dir = constants.dir;
571
572 for child in flex_items.iter_mut() {
573 let child_style = tree.get_style(child.node);
574
575 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
577 let child_parent_size = Size::NONE.with_cross(dir, cross_axis_parent_size);
578
579 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
581 let child_min_cross = child.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
582 let child_max_cross = child.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
583 let cross_axis_available_space: AvailableSpace = available_space
584 .cross(dir)
585 .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
586 .maybe_clamp(child_min_cross, child_max_cross);
587
588 let child_known_dimensions = {
590 let mut ckd = child.size.with_main(dir, None);
591 if child.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
592 ckd.set_cross(
593 dir,
594 cross_axis_available_space.into_option().maybe_sub(child.margin.cross_axis_sum(dir)),
595 );
596 }
597 ckd
598 };
599
600 child.flex_basis = 'flex_basis: {
601 let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(dir));
612 let main_size = child.size.main(dir);
613 if let Some(flex_basis) = flex_basis.or(main_size) {
614 break 'flex_basis flex_basis;
615 };
616
617 let child_available_space = Size::MAX_CONTENT
642 .with_main(
643 dir,
644 if available_space.main(dir) == AvailableSpace::MinContent {
646 AvailableSpace::MinContent
647 } else {
648 AvailableSpace::MaxContent
649 },
650 )
651 .with_cross(dir, cross_axis_available_space);
652
653 break 'flex_basis tree.measure_child_size(
654 child.node,
655 child_known_dimensions,
656 child_parent_size,
657 child_available_space,
658 SizingMode::ContentSize,
659 dir.main_axis(),
660 Line::FALSE,
661 );
662 };
663
664 let padding_border_sum = child.padding.main_axis_sum(constants.dir) + child.border.main_axis_sum(constants.dir);
672 child.flex_basis = child.flex_basis.max(padding_border_sum);
673
674 child.inner_flex_basis =
678 child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
679
680 let padding_border_axes_sums = (child.padding + child.border).sum_axes().map(Some);
681 let hypothetical_inner_min_main =
682 child.min_size.main(constants.dir).maybe_max(padding_border_axes_sums.main(constants.dir));
683 let hypothetical_inner_size =
684 child.flex_basis.maybe_clamp(hypothetical_inner_min_main, child.max_size.main(constants.dir));
685 let hypothetical_outer_size = hypothetical_inner_size + child.margin.main_axis_sum(constants.dir);
686
687 child.hypothetical_inner_size.set_main(constants.dir, hypothetical_inner_size);
688 child.hypothetical_outer_size.set_main(constants.dir, hypothetical_outer_size);
689
690 let style_min_main_size =
697 child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);
698
699 child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
700 let min_content_main_size = {
701 let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
702
703 tree.measure_child_size(
704 child.node,
705 child_known_dimensions,
706 child_parent_size,
707 child_available_space,
708 SizingMode::ContentSize,
709 dir.main_axis(),
710 Line::FALSE,
711 )
712 };
713
714 let clamped_min_content_size =
717 min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
718 clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
719 });
720 }
721}
722
723#[inline]
741fn collect_flex_lines<'a>(
742 constants: &AlgoConstants,
743 available_space: Size<AvailableSpace>,
744 flex_items: &'a mut Vec<FlexItem>,
745) -> Vec<FlexLine<'a>> {
746 if !constants.is_wrap {
747 let mut lines = new_vec_with_capacity(1);
748 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
749 lines
750 } else {
751 match available_space.main(constants.dir) {
752 AvailableSpace::MaxContent => {
755 let mut lines = new_vec_with_capacity(1);
756 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
757 lines
758 }
759 AvailableSpace::MinContent => {
762 let mut lines = new_vec_with_capacity(flex_items.len());
763 let mut items = &mut flex_items[..];
764 while !items.is_empty() {
765 let (line_items, rest) = items.split_at_mut(1);
766 lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
767 items = rest;
768 }
769 lines
770 }
771 AvailableSpace::Definite(main_axis_available_space) => {
772 let mut lines = new_vec_with_capacity(1);
773 let mut flex_items = &mut flex_items[..];
774 let main_axis_gap = constants.gap.main(constants.dir);
775
776 while !flex_items.is_empty() {
777 let mut line_length = 0.0;
780 let index = flex_items
781 .iter()
782 .enumerate()
783 .find(|&(idx, child)| {
784 let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
787 line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
788 line_length > main_axis_available_space && idx != 0
789 })
790 .map(|(idx, _)| idx)
791 .unwrap_or(flex_items.len());
792
793 let (items, rest) = flex_items.split_at_mut(index);
794 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
795 flex_items = rest;
796 }
797 lines
798 }
799 }
800 }
801}
802
803fn determine_container_main_size(
805 tree: &mut impl PartialLayoutTree,
806 available_space: Size<AvailableSpace>,
807 lines: &mut Vec<FlexLine<'_>>,
808 constants: &mut AlgoConstants,
809) {
810 let dir = constants.dir;
811 let main_content_box_inset = constants.content_box_inset.main_axis_sum(constants.dir);
812
813 let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
814 match available_space.main(dir) {
815 AvailableSpace::Definite(main_axis_available_space) => {
816 let longest_line_length: f32 = lines
817 .iter()
818 .map(|line| {
819 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
820 let total_target_size = line
821 .items
822 .iter()
823 .map(|child| {
824 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
825 (child.flex_basis + child.margin.main_axis_sum(constants.dir)).max(padding_border_sum)
826 })
827 .sum::<f32>();
828 total_target_size + line_main_axis_gap
829 })
830 .max_by(|a, b| a.total_cmp(b))
831 .unwrap_or(0.0);
832 let size = longest_line_length + main_content_box_inset;
833 if lines.len() > 1 {
834 f32_max(size, main_axis_available_space)
835 } else {
836 size
837 }
838 }
839 AvailableSpace::MinContent if constants.is_wrap => {
840 let longest_line_length: f32 = lines
841 .iter()
842 .map(|line| {
843 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
844 let total_target_size = line
845 .items
846 .iter()
847 .map(|child| {
848 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
849 (child.flex_basis + child.margin.main_axis_sum(constants.dir)).max(padding_border_sum)
850 })
851 .sum::<f32>();
852 total_target_size + line_main_axis_gap
853 })
854 .max_by(|a, b| a.total_cmp(b))
855 .unwrap_or(0.0);
856 longest_line_length + main_content_box_inset
857 }
858 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
859 let mut main_size = 0.0;
863
864 for line in lines.iter_mut() {
865 for item in line.items.iter_mut() {
866 let style_min = item.min_size.main(constants.dir);
867 let style_preferred = item.size.main(constants.dir);
868 let style_max = item.max_size.main(constants.dir);
869
870 let clamping_basis = Some(item.flex_basis).maybe_max(style_preferred);
878 let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
879 let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);
880
881 let min_main_size = style_min
882 .maybe_max(flex_basis_min)
883 .or(flex_basis_min)
884 .unwrap_or(item.resolved_minimum_main_size)
885 .max(item.resolved_minimum_main_size);
886 let max_main_size =
887 style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);
888
889 let content_contribution = match (min_main_size, style_preferred, max_main_size) {
890 (min, Some(pref), max) if max <= min || max <= pref => {
893 pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
894 }
895 (min, _, max) if max <= min => min + item.margin.main_axis_sum(constants.dir),
896 _ => {
899 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
901
902 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
904 let child_min_cross = item.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
905 let child_max_cross = item.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
906 let cross_axis_available_space: AvailableSpace = available_space
907 .cross(dir)
908 .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
909 .maybe_clamp(child_min_cross, child_max_cross);
910
911 let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
912
913 let content_main_size = tree.measure_child_size(
916 item.node,
917 Size::NONE,
918 constants.node_inner_size,
919 child_available_space,
920 SizingMode::InherentSize,
921 dir.main_axis(),
922 Line::FALSE,
923 ) + item.margin.main_axis_sum(constants.dir);
924
925 if constants.is_row {
938 content_main_size.maybe_clamp(style_min, style_max).max(main_content_box_inset)
939 } else {
940 content_main_size
941 .max(item.flex_basis)
942 .maybe_clamp(style_min, style_max)
943 .max(main_content_box_inset)
944 }
945 }
946 };
947 item.content_flex_fraction = {
948 let diff = content_contribution - item.flex_basis;
949 if diff > 0.0 {
950 diff / f32_max(1.0, item.flex_grow)
951 } else if diff < 0.0 {
952 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
953 diff / scaled_shrink_factor
954 } else {
955 0.0
957 }
958 };
959 }
960
961 let item_main_size_sum = line
979 .items
980 .iter_mut()
981 .map(|item| {
982 let flex_fraction = item.content_flex_fraction;
983 let flex_contribution = if item.content_flex_fraction > 0.0 {
986 f32_max(1.0, item.flex_grow) * flex_fraction
987 } else if item.content_flex_fraction < 0.0 {
988 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
989 scaled_shrink_factor * flex_fraction
990 } else {
991 0.0
992 };
993 let size = item.flex_basis + flex_contribution;
994 item.outer_target_size.set_main(constants.dir, size);
995 item.target_size.set_main(constants.dir, size);
996 size
997 })
998 .sum::<f32>();
999
1000 let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1001 main_size = f32_max(main_size, item_main_size_sum + gap_sum)
1002 }
1003
1004 main_size + main_content_box_inset
1005 }
1006 }
1007 });
1008
1009 let outer_main_size = outer_main_size
1010 .maybe_clamp(constants.min_size.main(constants.dir), constants.max_size.main(constants.dir))
1011 .max(main_content_box_inset - constants.scrollbar_gutter.main(constants.dir));
1012
1013 let inner_main_size = f32_max(outer_main_size - main_content_box_inset, 0.0);
1015 constants.container_size.set_main(constants.dir, outer_main_size);
1016 constants.inner_container_size.set_main(constants.dir, inner_main_size);
1017 constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
1018}
1019
1020#[inline]
1025fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants, original_gap: Size<f32>) {
1026 let total_original_main_axis_gap = sum_axis_gaps(original_gap.main(constants.dir), line.items.len());
1027 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1028
1029 let total_hypothetical_outer_main_size =
1035 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
1036 let used_flex_factor: f32 = total_original_main_axis_gap + total_hypothetical_outer_main_size;
1037 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1038 let shrinking = !growing;
1039
1040 for child in line.items.iter_mut() {
1048 let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
1049 child.target_size.set_main(constants.dir, inner_target_size);
1050
1051 if (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
1052 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1053 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1054 {
1055 child.frozen = true;
1056 let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1057 child.outer_target_size.set_main(constants.dir, outer_target_size);
1058 }
1059 }
1060
1061 let used_space: f32 = total_main_axis_gap
1066 + line
1067 .items
1068 .iter()
1069 .map(|child| {
1070 child.margin.main_axis_sum(constants.dir)
1071 + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
1072 })
1073 .sum::<f32>();
1074
1075 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1076
1077 loop {
1080 if line.items.iter().all(|child| child.frozen) {
1084 break;
1085 }
1086
1087 let used_space: f32 = total_main_axis_gap
1094 + line
1095 .items
1096 .iter()
1097 .map(|child| {
1098 child.margin.main_axis_sum(constants.dir)
1099 + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
1100 })
1101 .sum::<f32>();
1102
1103 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1104
1105 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1106 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1107 (flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
1108 });
1109
1110 let free_space = if growing && sum_flex_grow < 1.0 {
1111 (initial_free_space * sum_flex_grow - total_main_axis_gap)
1112 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1113 } else if shrinking && sum_flex_shrink < 1.0 {
1114 (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1115 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1116 } else {
1117 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1118 .unwrap_or(used_flex_factor - used_space)
1119 };
1120
1121 if free_space.is_normal() {
1141 if growing && sum_flex_grow > 0.0 {
1142 for child in &mut unfrozen {
1143 child
1144 .target_size
1145 .set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
1146 }
1147 } else if shrinking && sum_flex_shrink > 0.0 {
1148 let sum_scaled_shrink_factor: f32 =
1149 unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
1150
1151 if sum_scaled_shrink_factor > 0.0 {
1152 for child in &mut unfrozen {
1153 let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
1154 child.target_size.set_main(
1155 constants.dir,
1156 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1157 )
1158 }
1159 }
1160 }
1161 }
1162
1163 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1169 let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
1170 let max_main = child.max_size.main(constants.dir);
1171 let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1172 child.violation = clamped - child.target_size.main(constants.dir);
1173 child.target_size.set_main(constants.dir, clamped);
1174 child.outer_target_size.set_main(
1175 constants.dir,
1176 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1177 );
1178
1179 acc + child.violation
1180 });
1181
1182 for child in &mut unfrozen {
1192 match total_violation {
1193 v if v > 0.0 => child.frozen = child.violation > 0.0,
1194 v if v < 0.0 => child.frozen = child.violation < 0.0,
1195 _ => child.frozen = true,
1196 }
1197 }
1198
1199 }
1201}
1202
1203#[inline]
1210fn determine_hypothetical_cross_size(
1211 tree: &mut impl PartialLayoutTree,
1212 line: &mut FlexLine,
1213 constants: &AlgoConstants,
1214 available_space: Size<AvailableSpace>,
1215) {
1216 for child in line.items.iter_mut() {
1217 let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
1218
1219 let child_known_main = constants.container_size.main(constants.dir).into();
1220
1221 let child_cross = child
1222 .size
1223 .cross(constants.dir)
1224 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1225 .maybe_max(padding_border_sum);
1226
1227 let child_available_cross = available_space
1228 .cross(constants.dir)
1229 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1230 .maybe_max(padding_border_sum);
1231
1232 let child_inner_cross = child_cross.unwrap_or_else(|| {
1233 tree.measure_child_size(
1234 child.node,
1235 Size {
1236 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1237 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1238 },
1239 constants.node_inner_size,
1240 Size {
1241 width: if constants.is_row { child_known_main } else { child_available_cross },
1242 height: if constants.is_row { child_available_cross } else { child_known_main },
1243 },
1244 SizingMode::ContentSize,
1245 constants.dir.cross_axis(),
1246 Line::FALSE,
1247 )
1248 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1249 .max(padding_border_sum)
1250 });
1251 let child_outer_cross = child_inner_cross + child.margin.cross_axis_sum(constants.dir);
1252
1253 child.hypothetical_inner_size.set_cross(constants.dir, child_inner_cross);
1254 child.hypothetical_outer_size.set_cross(constants.dir, child_outer_cross);
1255 }
1256}
1257
1258#[inline]
1260fn calculate_children_base_lines(
1261 tree: &mut impl PartialLayoutTree,
1262 node_size: Size<Option<f32>>,
1263 available_space: Size<AvailableSpace>,
1264 flex_lines: &mut [FlexLine],
1265 constants: &AlgoConstants,
1266) {
1267 if !constants.is_row {
1271 return;
1272 }
1273
1274 for line in flex_lines {
1275 let line_baseline_child_count =
1277 line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1278 if line_baseline_child_count <= 1 {
1279 continue;
1280 }
1281
1282 for child in line.items.iter_mut() {
1283 if child.align_self != AlignSelf::Baseline {
1285 continue;
1286 }
1287
1288 let measured_size_and_baselines = tree.perform_child_layout(
1289 child.node,
1290 Size {
1291 width: if constants.is_row {
1292 child.target_size.width.into()
1293 } else {
1294 child.hypothetical_inner_size.width.into()
1295 },
1296 height: if constants.is_row {
1297 child.hypothetical_inner_size.height.into()
1298 } else {
1299 child.target_size.height.into()
1300 },
1301 },
1302 constants.node_inner_size,
1303 Size {
1304 width: if constants.is_row {
1305 constants.container_size.width.into()
1306 } else {
1307 available_space.width.maybe_set(node_size.width)
1308 },
1309 height: if constants.is_row {
1310 available_space.height.maybe_set(node_size.height)
1311 } else {
1312 constants.container_size.height.into()
1313 },
1314 },
1315 SizingMode::ContentSize,
1316 Line::FALSE,
1317 );
1318
1319 let baseline = measured_size_and_baselines.first_baselines.y;
1320 let height = measured_size_and_baselines.size.height;
1321
1322 child.baseline = baseline.unwrap_or(height) + child.margin.top;
1323 }
1324 }
1325}
1326
1327#[inline]
1348fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1349 if flex_lines.len() == 1
1353 && node_size.cross(constants.dir).is_some()
1354 && (!constants.is_wrap
1355 || matches!(
1356 constants.align_content,
1357 AlignContent::Stretch | AlignContent::SpaceEvenly | AlignContent::SpaceAround
1358 ))
1359 {
1360 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1361 let cross_min_size = constants.min_size.cross(constants.dir);
1362 let cross_max_size = constants.max_size.cross(constants.dir);
1363 flex_lines[0].cross_size = node_size
1364 .cross(constants.dir)
1365 .maybe_clamp(cross_min_size, cross_max_size)
1366 .maybe_sub(cross_axis_padding_border)
1367 .maybe_max(0.0)
1368 .unwrap_or(0.0);
1369 } else {
1370 for line in flex_lines.iter_mut() {
1371 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1384 line.cross_size = line
1385 .items
1386 .iter()
1387 .map(|child| {
1388 if child.align_self == AlignSelf::Baseline
1389 && !child.margin_is_auto.cross_start(constants.dir)
1390 && !child.margin_is_auto.cross_end(constants.dir)
1391 {
1392 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1393 } else {
1394 child.hypothetical_outer_size.cross(constants.dir)
1395 }
1396 })
1397 .fold(0.0, |acc, x| acc.max(x));
1398 }
1399 }
1400}
1401
1402#[inline]
1410fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1411 if constants.align_content == AlignContent::Stretch {
1412 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1413 let cross_min_size = constants.min_size.cross(constants.dir);
1414 let cross_max_size = constants.max_size.cross(constants.dir);
1415 let container_min_inner_cross = node_size
1416 .cross(constants.dir)
1417 .or(cross_min_size)
1418 .maybe_clamp(cross_min_size, cross_max_size)
1419 .maybe_sub(cross_axis_padding_border)
1420 .maybe_max(0.0)
1421 .unwrap_or(0.0);
1422
1423 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1424 let lines_total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1425
1426 if lines_total_cross < container_min_inner_cross {
1427 let remaining = container_min_inner_cross - lines_total_cross;
1428 let addition = remaining / flex_lines.len() as f32;
1429 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1430 }
1431 }
1432}
1433
1434#[inline]
1446fn determine_used_cross_size(tree: &impl PartialLayoutTree, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1447 for line in flex_lines {
1448 let line_cross_size = line.cross_size;
1449
1450 for child in line.items.iter_mut() {
1451 let child_style = tree.get_style(child.node);
1452 child.target_size.set_cross(
1453 constants.dir,
1454 if child.align_self == AlignSelf::Stretch
1455 && !child.margin_is_auto.cross_start(constants.dir)
1456 && !child.margin_is_auto.cross_end(constants.dir)
1457 && child_style.size.cross(constants.dir) == Dimension::Auto
1458 {
1459 let max_size_ignoring_aspect_ratio = child_style.max_size.maybe_resolve(constants.node_inner_size);
1463
1464 (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1465 child.min_size.cross(constants.dir),
1466 max_size_ignoring_aspect_ratio.cross(constants.dir),
1467 )
1468 } else {
1469 child.hypothetical_inner_size.cross(constants.dir)
1470 },
1471 );
1472
1473 child.outer_target_size.set_cross(
1474 constants.dir,
1475 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1476 );
1477 }
1478 }
1479}
1480
1481#[inline]
1492fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1493 for line in flex_lines {
1494 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1495 let used_space: f32 = total_main_axis_gap
1496 + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1497 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1498 let mut num_auto_margins = 0;
1499
1500 for child in line.items.iter_mut() {
1501 if child.margin_is_auto.main_start(constants.dir) {
1502 num_auto_margins += 1;
1503 }
1504 if child.margin_is_auto.main_end(constants.dir) {
1505 num_auto_margins += 1;
1506 }
1507 }
1508
1509 if free_space > 0.0 && num_auto_margins > 0 {
1510 let margin = free_space / num_auto_margins as f32;
1511
1512 for child in line.items.iter_mut() {
1513 if child.margin_is_auto.main_start(constants.dir) {
1514 if constants.is_row {
1515 child.margin.left = margin;
1516 } else {
1517 child.margin.top = margin;
1518 }
1519 }
1520 if child.margin_is_auto.main_end(constants.dir) {
1521 if constants.is_row {
1522 child.margin.right = margin;
1523 } else {
1524 child.margin.bottom = margin;
1525 }
1526 }
1527 }
1528 } else {
1529 let num_items = line.items.len();
1530 let layout_reverse = constants.dir.is_reverse();
1531 let gap = constants.gap.main(constants.dir);
1532 let justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
1533
1534 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1535 child.offset_main =
1536 compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1537 };
1538
1539 if layout_reverse {
1540 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1541 } else {
1542 line.items.iter_mut().enumerate().for_each(justify_item);
1543 }
1544 }
1545 }
1546}
1547
1548#[inline]
1561fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1562 for line in flex_lines {
1563 let line_cross_size = line.cross_size;
1564 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1565
1566 for child in line.items.iter_mut() {
1567 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1568
1569 if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
1570 if constants.is_row {
1571 child.margin.top = free_space / 2.0;
1572 child.margin.bottom = free_space / 2.0;
1573 } else {
1574 child.margin.left = free_space / 2.0;
1575 child.margin.right = free_space / 2.0;
1576 }
1577 } else if child.margin_is_auto.cross_start(constants.dir) {
1578 if constants.is_row {
1579 child.margin.top = free_space;
1580 } else {
1581 child.margin.left = free_space;
1582 }
1583 } else if child.margin_is_auto.cross_end(constants.dir) {
1584 if constants.is_row {
1585 child.margin.bottom = free_space;
1586 } else {
1587 child.margin.right = free_space;
1588 }
1589 } else {
1590 child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1592 }
1593 }
1594 }
1595}
1596
1597#[inline]
1604fn align_flex_items_along_cross_axis(
1605 child: &FlexItem,
1606 free_space: f32,
1607 max_baseline: f32,
1608 constants: &AlgoConstants,
1609) -> f32 {
1610 match child.align_self {
1611 AlignSelf::Start => 0.0,
1612 AlignSelf::FlexStart => {
1613 if constants.is_wrap_reverse {
1614 free_space
1615 } else {
1616 0.0
1617 }
1618 }
1619 AlignSelf::End => free_space,
1620 AlignSelf::FlexEnd => {
1621 if constants.is_wrap_reverse {
1622 0.0
1623 } else {
1624 free_space
1625 }
1626 }
1627 AlignSelf::Center => free_space / 2.0,
1628 AlignSelf::Baseline => {
1629 if constants.is_row {
1630 max_baseline - child.baseline
1631 } else {
1632 if constants.is_wrap_reverse {
1635 free_space
1636 } else {
1637 0.0
1638 }
1639 }
1640 }
1641 AlignSelf::Stretch => {
1642 if constants.is_wrap_reverse {
1643 free_space
1644 } else {
1645 0.0
1646 }
1647 }
1648 }
1649}
1650
1651#[inline]
1661#[must_use]
1662fn determine_container_cross_size(
1663 flex_lines: &[FlexLine],
1664 node_size: Size<Option<f32>>,
1665 constants: &mut AlgoConstants,
1666) -> f32 {
1667 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1668 let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1669
1670 let padding_border_sum = constants.content_box_inset.cross_axis_sum(constants.dir);
1671 let cross_scrollbar_gutter = constants.scrollbar_gutter.cross(constants.dir);
1672 let min_cross_size = constants.min_size.cross(constants.dir);
1673 let max_cross_size = constants.max_size.cross(constants.dir);
1674 let outer_container_size = node_size
1675 .cross(constants.dir)
1676 .unwrap_or(total_line_cross_size + total_cross_axis_gap + padding_border_sum)
1677 .maybe_clamp(min_cross_size, max_cross_size)
1678 .max(padding_border_sum - cross_scrollbar_gutter);
1679 let inner_container_size = f32_max(outer_container_size - padding_border_sum, 0.0);
1680
1681 constants.container_size.set_cross(constants.dir, outer_container_size);
1682 constants.inner_container_size.set_cross(constants.dir, inner_container_size);
1683
1684 total_line_cross_size
1685}
1686
1687#[inline]
1693fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
1694 let num_lines = flex_lines.len();
1695 let gap = constants.gap.cross(constants.dir);
1696 let align_content_mode = constants.align_content;
1697 let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1698 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1699
1700 let align_line = |(i, line): (usize, &mut FlexLine)| {
1701 line.offset_cross =
1702 compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1703 };
1704
1705 if constants.is_wrap_reverse {
1706 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1707 } else {
1708 flex_lines.iter_mut().enumerate().for_each(align_line);
1709 }
1710}
1711
1712#[allow(clippy::too_many_arguments)]
1714fn calculate_flex_item(
1715 tree: &mut impl PartialLayoutTree,
1716 item: &mut FlexItem,
1717 total_offset_main: &mut f32,
1718 total_offset_cross: f32,
1719 line_offset_cross: f32,
1720 #[cfg(feature = "content_size")] total_content_size: &mut Size<f32>,
1721 container_size: Size<f32>,
1722 node_inner_size: Size<Option<f32>>,
1723 direction: FlexDirection,
1724) {
1725 let layout_output = tree.perform_child_layout(
1726 item.node,
1727 item.target_size.map(|s| s.into()),
1728 node_inner_size,
1729 container_size.map(|s| s.into()),
1730 SizingMode::ContentSize,
1731 Line::FALSE,
1732 );
1733 let LayoutOutput {
1734 size,
1735 #[cfg(feature = "content_size")]
1736 content_size,
1737 ..
1738 } = layout_output;
1739
1740 let offset_main = *total_offset_main
1741 + item.offset_main
1742 + item.margin.main_start(direction)
1743 + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1744
1745 let offset_cross = total_offset_cross
1746 + item.offset_cross
1747 + line_offset_cross
1748 + item.margin.cross_start(direction)
1749 + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1750
1751 if direction.is_row() {
1752 let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1753 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1754 item.baseline = baseline_offset_cross + inner_baseline;
1755 } else {
1756 let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1757 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1758 item.baseline = baseline_offset_main + inner_baseline;
1759 }
1760
1761 let location = match direction.is_row() {
1762 true => Point { x: offset_main, y: offset_cross },
1763 false => Point { x: offset_cross, y: offset_main },
1764 };
1765 let scrollbar_size = Size {
1766 width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1767 height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1768 };
1769
1770 tree.set_unrounded_layout(
1771 item.node,
1772 &Layout {
1773 order: item.order,
1774 size,
1775 #[cfg(feature = "content_size")]
1776 content_size,
1777 scrollbar_size,
1778 location,
1779 padding: item.padding,
1780 border: item.border,
1781 },
1782 );
1783
1784 *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
1785
1786 #[cfg(feature = "content_size")]
1787 {
1788 *total_content_size =
1789 total_content_size.f32_max(compute_content_size_contribution(location, size, content_size, item.overflow));
1790 }
1791}
1792
1793#[allow(clippy::too_many_arguments)]
1795fn calculate_layout_line(
1796 tree: &mut impl PartialLayoutTree,
1797 line: &mut FlexLine,
1798 total_offset_cross: &mut f32,
1799 #[cfg(feature = "content_size")] content_size: &mut Size<f32>,
1800 container_size: Size<f32>,
1801 node_inner_size: Size<Option<f32>>,
1802 padding_border: Rect<f32>,
1803 direction: FlexDirection,
1804) {
1805 let mut total_offset_main = padding_border.main_start(direction);
1806 let line_offset_cross = line.offset_cross;
1807
1808 if direction.is_reverse() {
1809 for item in line.items.iter_mut().rev() {
1810 calculate_flex_item(
1811 tree,
1812 item,
1813 &mut total_offset_main,
1814 *total_offset_cross,
1815 line_offset_cross,
1816 #[cfg(feature = "content_size")]
1817 content_size,
1818 container_size,
1819 node_inner_size,
1820 direction,
1821 );
1822 }
1823 } else {
1824 for item in line.items.iter_mut() {
1825 calculate_flex_item(
1826 tree,
1827 item,
1828 &mut total_offset_main,
1829 *total_offset_cross,
1830 line_offset_cross,
1831 #[cfg(feature = "content_size")]
1832 content_size,
1833 container_size,
1834 node_inner_size,
1835 direction,
1836 );
1837 }
1838 }
1839
1840 *total_offset_cross += line_offset_cross + line.cross_size;
1841}
1842
1843#[inline]
1845fn final_layout_pass(
1846 tree: &mut impl PartialLayoutTree,
1847 flex_lines: &mut [FlexLine],
1848 constants: &AlgoConstants,
1849) -> Size<f32> {
1850 let mut total_offset_cross = constants.content_box_inset.cross_start(constants.dir);
1851
1852 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
1853 let mut content_size = Size::ZERO;
1854
1855 if constants.is_wrap_reverse {
1856 for line in flex_lines.iter_mut().rev() {
1857 calculate_layout_line(
1858 tree,
1859 line,
1860 &mut total_offset_cross,
1861 #[cfg(feature = "content_size")]
1862 &mut content_size,
1863 constants.container_size,
1864 constants.node_inner_size,
1865 constants.content_box_inset,
1866 constants.dir,
1867 );
1868 }
1869 } else {
1870 for line in flex_lines.iter_mut() {
1871 calculate_layout_line(
1872 tree,
1873 line,
1874 &mut total_offset_cross,
1875 #[cfg(feature = "content_size")]
1876 &mut content_size,
1877 constants.container_size,
1878 constants.node_inner_size,
1879 constants.content_box_inset,
1880 constants.dir,
1881 );
1882 }
1883 }
1884
1885 content_size
1886}
1887
1888#[inline]
1890fn perform_absolute_layout_on_absolute_children(
1891 tree: &mut impl PartialLayoutTree,
1892 node: NodeId,
1893 constants: &AlgoConstants,
1894) -> Size<f32> {
1895 let container_width = constants.container_size.width;
1896 let container_height = constants.container_size.height;
1897
1898 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
1899 let mut content_size = Size::ZERO;
1900
1901 for order in 0..tree.child_count(node) {
1902 let child = tree.get_child_id(node, order);
1903 let child_style = tree.get_style(child);
1904
1905 if child_style.display == Display::None || child_style.position != Position::Absolute {
1907 continue;
1908 }
1909
1910 let overflow = child_style.overflow;
1911 let scrollbar_width = child_style.scrollbar_width;
1912 let aspect_ratio = child_style.aspect_ratio;
1913 let align_self = child_style.align_self.unwrap_or(constants.align_items);
1914 let margin = child_style.margin.map(|margin| margin.resolve_to_option(container_width));
1915 let padding = child_style.padding.resolve_or_zero(Some(container_width));
1916 let border = child_style.border.resolve_or_zero(Some(container_width));
1917 let padding_border_sum = (padding + border).sum_axes();
1918
1919 let left = child_style.inset.left.maybe_resolve(container_width);
1921 let right = child_style.inset.right.maybe_resolve(container_width);
1922 let top = child_style.inset.top.maybe_resolve(container_height);
1923 let bottom = child_style.inset.bottom.maybe_resolve(container_height);
1924
1925 let style_size =
1927 child_style.size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1928 let min_size = child_style
1929 .min_size
1930 .maybe_resolve(constants.container_size)
1931 .maybe_apply_aspect_ratio(aspect_ratio)
1932 .or(padding_border_sum.map(Some))
1933 .maybe_max(padding_border_sum);
1934 let max_size =
1935 child_style.max_size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1936 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
1937
1938 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
1942 let new_width_raw = container_width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
1943 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
1944 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
1945 }
1946
1947 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
1951 let new_height_raw = container_height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
1952 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
1953 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
1954 }
1955
1956 let layout_output = tree.perform_child_layout(
1957 child,
1958 known_dimensions,
1959 constants.node_inner_size,
1960 Size {
1961 width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
1962 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
1963 },
1964 SizingMode::ContentSize,
1965 Line::FALSE,
1966 );
1967 let measured_size = layout_output.size;
1968 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
1969
1970 let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
1971
1972 let free_space = Size {
1973 width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
1974 height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
1975 }
1976 .f32_max(Size::ZERO);
1977
1978 let resolved_margin = {
1980 let auto_margin_size = Size {
1981 width: {
1982 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
1983 if auto_margin_count > 0 {
1984 free_space.width / auto_margin_count as f32
1985 } else {
1986 0.0
1987 }
1988 },
1989 height: {
1990 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
1991 if auto_margin_count > 0 {
1992 free_space.height / auto_margin_count as f32
1993 } else {
1994 0.0
1995 }
1996 },
1997 };
1998
1999 Rect {
2000 left: margin.left.unwrap_or(auto_margin_size.width),
2001 right: margin.right.unwrap_or(auto_margin_size.width),
2002 top: margin.top.unwrap_or(auto_margin_size.height),
2003 bottom: margin.bottom.unwrap_or(auto_margin_size.height),
2004 }
2005 };
2006
2007 let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
2009 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
2010
2011 let offset_main = if let Some(start) = start_main {
2014 start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2015 } else if let Some(end) = end_main {
2016 constants.container_size.main(constants.dir)
2017 - constants.border.main_end(constants.dir)
2018 - final_size.main(constants.dir)
2019 - end
2020 - resolved_margin.main_end(constants.dir)
2021 } else {
2022 match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
2025 (JustifyContent::SpaceBetween, _)
2026 | (JustifyContent::Start, _)
2027 | (JustifyContent::Stretch, false)
2028 | (JustifyContent::FlexStart, false)
2029 | (JustifyContent::FlexEnd, true) => {
2030 constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2031 }
2032 (JustifyContent::End, _)
2033 | (JustifyContent::FlexEnd, false)
2034 | (JustifyContent::FlexStart, true)
2035 | (JustifyContent::Stretch, true) => {
2036 constants.container_size.main(constants.dir)
2037 - constants.content_box_inset.main_end(constants.dir)
2038 - final_size.main(constants.dir)
2039 - resolved_margin.main_end(constants.dir)
2040 }
2041 (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
2042 (constants.container_size.main(constants.dir)
2043 + constants.content_box_inset.main_start(constants.dir)
2044 - constants.content_box_inset.main_end(constants.dir)
2045 - final_size.main(constants.dir)
2046 + resolved_margin.main_start(constants.dir)
2047 - resolved_margin.main_end(constants.dir))
2048 / 2.0
2049 }
2050 }
2051 };
2052
2053 let offset_cross = if let Some(start) = start_cross {
2056 start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2057 } else if let Some(end) = end_cross {
2058 constants.container_size.cross(constants.dir)
2059 - constants.border.cross_end(constants.dir)
2060 - final_size.cross(constants.dir)
2061 - end
2062 - resolved_margin.cross_end(constants.dir)
2063 } else {
2064 match (align_self, constants.is_wrap_reverse) {
2065 (AlignSelf::Start, _)
2069 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
2070 | (AlignSelf::FlexEnd, true) => {
2071 constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2072 }
2073 (AlignSelf::End, _)
2074 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
2075 | (AlignSelf::FlexEnd, false) => {
2076 constants.container_size.cross(constants.dir)
2077 - constants.content_box_inset.cross_end(constants.dir)
2078 - final_size.cross(constants.dir)
2079 - resolved_margin.cross_end(constants.dir)
2080 }
2081 (AlignSelf::Center, _) => {
2082 (constants.container_size.cross(constants.dir)
2083 + constants.content_box_inset.cross_start(constants.dir)
2084 - constants.content_box_inset.cross_end(constants.dir)
2085 - final_size.cross(constants.dir)
2086 + resolved_margin.cross_start(constants.dir)
2087 - resolved_margin.cross_end(constants.dir))
2088 / 2.0
2089 }
2090 }
2091 };
2092
2093 let location = match constants.is_row {
2094 true => Point { x: offset_main, y: offset_cross },
2095 false => Point { x: offset_cross, y: offset_main },
2096 };
2097 let scrollbar_size = Size {
2098 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
2099 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
2100 };
2101 tree.set_unrounded_layout(
2102 child,
2103 &Layout {
2104 order: order as u32,
2105 size: final_size,
2106 #[cfg(feature = "content_size")]
2107 content_size: layout_output.content_size,
2108 scrollbar_size,
2109 location,
2110 padding,
2111 border,
2112 },
2113 );
2114
2115 #[cfg(feature = "content_size")]
2116 {
2117 let size_content_size_contribution = Size {
2118 width: match overflow.x {
2119 Overflow::Visible => f32_max(final_size.width, layout_output.content_size.width),
2120 _ => final_size.width,
2121 },
2122 height: match overflow.y {
2123 Overflow::Visible => f32_max(final_size.height, layout_output.content_size.height),
2124 _ => final_size.height,
2125 },
2126 };
2127 if size_content_size_contribution.has_non_zero_area() {
2128 let content_size_contribution = Size {
2129 width: location.x + size_content_size_contribution.width,
2130 height: location.y + size_content_size_contribution.height,
2131 };
2132 content_size = content_size.f32_max(content_size_contribution);
2133 }
2134 }
2135 }
2136
2137 content_size
2138}
2139
2140#[inline(always)]
2144fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
2145 if num_items <= 1 {
2147 0.0
2149 } else {
2150 gap * (num_items - 1) as f32
2152 }
2153}
2154
2155#[cfg(test)]
2156mod tests {
2157 #![allow(clippy::redundant_clone)]
2158
2159 use crate::{
2160 geometry::Size,
2161 style::{FlexWrap, Style},
2162 util::{MaybeMath, ResolveOrZero},
2163 TaffyTree,
2164 };
2165
2166 #[test]
2168 fn correct_constants() {
2169 let mut tree: TaffyTree<()> = TaffyTree::with_capacity(16);
2170
2171 let style = Style::default();
2172 let node_id = tree.new_leaf(style.clone()).unwrap();
2173
2174 let node_size = Size::NONE;
2175 let parent_size = Size::NONE;
2176
2177 let constants = super::compute_constants(tree.style(node_id).unwrap(), node_size, parent_size);
2178
2179 assert!(constants.dir == style.flex_direction);
2180 assert!(constants.is_row == style.flex_direction.is_row());
2181 assert!(constants.is_column == style.flex_direction.is_column());
2182 assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
2183
2184 let margin = style.margin.resolve_or_zero(parent_size);
2185 assert_eq!(constants.margin, margin);
2186
2187 let border = style.border.resolve_or_zero(parent_size);
2188 let padding = style.padding.resolve_or_zero(parent_size);
2189 let padding_border = padding + border;
2190 assert_eq!(constants.border, border);
2191 assert_eq!(constants.content_box_inset, padding_border);
2192
2193 let inner_size = Size {
2194 width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
2195 height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
2196 };
2197 assert_eq!(constants.node_inner_size, inner_size);
2198
2199 assert_eq!(constants.container_size, Size::zero());
2200 assert_eq!(constants.inner_container_size, Size::zero());
2201 }
2202}