use crate::compute::common::alignment::compute_alignment_offset;
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{
AlignContent, AlignItems, AlignSelf, AvailableSpace, FlexWrap, JustifyContent, LengthPercentageAuto, Overflow,
Position,
};
use crate::style::{CoreStyle, FlexDirection, FlexboxContainerStyle, FlexboxItemStyle};
use crate::style_helpers::{TaffyMaxContent, TaffyMinContent};
use crate::tree::{Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
use crate::tree::{LayoutFlexboxContainer, LayoutPartialTreeExt, NodeId};
use crate::util::debug::debug_log;
use crate::util::sys::{f32_max, new_vec_with_capacity, Vec};
use crate::util::MaybeMath;
use crate::util::{MaybeResolve, ResolveOrZero};
use crate::{BoxGenerationMode, BoxSizing, Direction};
use super::common::alignment::apply_alignment_fallback;
#[cfg(feature = "content_size")]
use super::common::content_size::compute_content_size_contribution;
struct FlexItem {
node: NodeId,
order: u32,
size: Size<Option<f32>>,
min_size: Size<Option<f32>>,
max_size: Size<Option<f32>>,
align_self: AlignSelf,
overflow: Point<Overflow>,
scrollbar_width: f32,
flex_shrink: f32,
flex_grow: f32,
resolved_minimum_main_size: f32,
inset: Rect<Option<f32>>,
margin: Rect<f32>,
margin_is_auto: Rect<bool>,
padding: Rect<f32>,
border: Rect<f32>,
flex_basis: f32,
inner_flex_basis: f32,
violation: f32,
frozen: bool,
content_flex_fraction: f32,
hypothetical_inner_size: Size<f32>,
hypothetical_outer_size: Size<f32>,
target_size: Size<f32>,
outer_target_size: Size<f32>,
baseline: f32,
offset_main: f32,
offset_cross: f32,
}
impl FlexItem {
fn is_scroll_container(&self) -> bool {
self.overflow.x.is_scroll_container() | self.overflow.y.is_scroll_container()
}
}
struct FlexLine<'a> {
items: &'a mut [FlexItem],
cross_size: f32,
offset_cross: f32,
}
struct AlgoConstants {
dir: FlexDirection,
layout_direction: Direction,
is_row: bool,
is_column: bool,
is_wrap: bool,
is_wrap_reverse: bool,
min_size: Size<Option<f32>>,
max_size: Size<Option<f32>>,
margin: Rect<f32>,
border: Rect<f32>,
content_box_inset: Rect<f32>,
scrollbar_gutter: Point<f32>,
gap: Size<f32>,
align_items: AlignItems,
align_content: AlignContent,
justify_content: Option<JustifyContent>,
node_outer_size: Size<Option<f32>>,
node_inner_size: Size<Option<f32>>,
container_size: Size<f32>,
inner_container_size: Size<f32>,
}
pub fn compute_flexbox_layout(
tree: &mut impl LayoutFlexboxContainer,
node: NodeId,
inputs: LayoutInput,
) -> LayoutOutput {
let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
let style = tree.get_flexbox_container_style(node);
let aspect_ratio = style.aspect_ratio();
let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
let padding_border_sum = padding.sum_axes() + border.sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
let min_size = style
.min_size()
.maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = style
.max_size()
.maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
style
.size()
.maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment)
.maybe_clamp(min_size, max_size)
} else {
Size::NONE
};
let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
(Some(min), Some(max)) if max <= min => Some(min),
_ => None,
});
let styled_based_known_dimensions =
known_dimensions.or(min_max_definite_size.or(clamped_style_size).maybe_max(padding_border_sum));
if run_mode == RunMode::ComputeSize {
if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
return LayoutOutput::from_outer_size(Size { width, height });
}
}
debug_log!("FLEX:", dbg:style.flex_direction());
drop(style);
compute_preliminary(tree, node, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
}
fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
let mut constants = compute_constants(tree, tree.get_flexbox_container_style(node), known_dimensions, parent_size);
debug_log!("generate_anonymous_flex_items");
let mut flex_items = generate_anonymous_flex_items(tree, node, &constants);
debug_log!("determine_available_space");
let available_space = determine_available_space(known_dimensions, available_space, &constants);
debug_log!("determine_flex_base_size");
determine_flex_base_size(tree, &constants, available_space, &mut flex_items);
#[cfg(feature = "debug")]
for item in flex_items.iter() {
debug_log!("item.flex_basis", item.flex_basis);
debug_log!("item.inner_flex_basis", item.inner_flex_basis);
debug_log!("item.hypothetical_outer_size", dbg:item.hypothetical_outer_size);
debug_log!("item.hypothetical_inner_size", dbg:item.hypothetical_inner_size);
debug_log!("item.resolved_minimum_main_size", dbg:item.resolved_minimum_main_size);
}
debug_log!("collect_flex_lines");
let mut flex_lines = collect_flex_lines(&constants, available_space, &mut flex_items);
debug_log!("determine_container_main_size");
if let Some(inner_main_size) = constants.node_inner_size.main(constants.dir) {
let outer_main_size = inner_main_size + constants.content_box_inset.main_axis_sum(constants.dir);
constants.inner_container_size.set_main(constants.dir, inner_main_size);
constants.container_size.set_main(constants.dir, outer_main_size);
} else {
determine_container_main_size(tree, available_space, &mut flex_lines, &mut constants);
constants.node_inner_size.set_main(constants.dir, Some(constants.inner_container_size.main(constants.dir)));
constants.node_outer_size.set_main(constants.dir, Some(constants.container_size.main(constants.dir)));
debug_log!("constants.node_outer_size", dbg:constants.node_outer_size);
debug_log!("constants.node_inner_size", dbg:constants.node_inner_size);
let style = tree.get_flexbox_container_style(node);
let inner_container_size = constants.inner_container_size.main(constants.dir);
let new_gap = style
.gap()
.main(constants.dir)
.maybe_resolve(inner_container_size, |val, basis| tree.calc(val, basis))
.unwrap_or(0.0);
constants.gap.set_main(constants.dir, new_gap);
}
debug_log!("resolve_flexible_lengths");
for line in &mut flex_lines {
resolve_flexible_lengths(line, &constants);
}
debug_log!("determine_hypothetical_cross_size");
for line in &mut flex_lines {
determine_hypothetical_cross_size(tree, line, &constants, available_space);
}
debug_log!("calculate_children_base_lines");
calculate_children_base_lines(tree, known_dimensions, available_space, &mut flex_lines, &constants);
debug_log!("calculate_cross_size");
calculate_cross_size(&mut flex_lines, known_dimensions, &constants);
debug_log!("handle_align_content_stretch");
handle_align_content_stretch(&mut flex_lines, known_dimensions, &constants);
debug_log!("determine_used_cross_size");
determine_used_cross_size(tree, &mut flex_lines, &constants);
debug_log!("distribute_remaining_free_space");
distribute_remaining_free_space(&mut flex_lines, &constants);
debug_log!("resolve_cross_axis_auto_margins");
resolve_cross_axis_auto_margins(&mut flex_lines, &constants);
debug_log!("determine_container_cross_size");
let total_line_cross_size = determine_container_cross_size(&flex_lines, known_dimensions, &mut constants);
if run_mode == RunMode::ComputeSize {
return LayoutOutput::from_outer_size(constants.container_size);
}
debug_log!("align_flex_lines_per_align_content");
align_flex_lines_per_align_content(&mut flex_lines, &constants, total_line_cross_size);
debug_log!("final_layout_pass");
let inflow_content_size = final_layout_pass(tree, &mut flex_lines, &constants);
debug_log!("perform_absolute_layout_on_absolute_children");
let absolute_content_size = perform_absolute_layout_on_absolute_children(tree, node, &constants);
debug_log!("hidden_layout");
let len = tree.child_count(node);
for order in 0..len {
let child = tree.get_child_id(node, order);
if tree.get_flexbox_child_style(child).box_generation_mode() == BoxGenerationMode::None {
tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
tree.perform_child_layout(
child,
Size::NONE,
Size::NONE,
Size::MAX_CONTENT,
SizingMode::InherentSize,
Line::FALSE,
);
}
}
let first_vertical_baseline = if flex_lines.is_empty() {
None
} else {
flex_lines[0]
.items
.iter()
.find(|item| constants.is_column || item.align_self == AlignSelf::Baseline)
.or_else(|| flex_lines[0].items.iter().next())
.map(|child| {
let offset_vertical = if constants.is_row { child.offset_cross } else { child.offset_main };
offset_vertical + child.baseline
})
};
LayoutOutput::from_sizes_and_baselines(
constants.container_size,
inflow_content_size.f32_max(absolute_content_size),
Point { x: None, y: first_vertical_baseline },
)
}
#[inline]
fn compute_constants(
tree: &impl LayoutFlexboxContainer,
style: impl FlexboxContainerStyle,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
) -> AlgoConstants {
let dir = style.flex_direction();
let is_row = dir.is_row();
let is_column = dir.is_column();
let is_wrap = matches!(style.flex_wrap(), FlexWrap::Wrap | FlexWrap::WrapReverse);
let is_wrap_reverse = style.flex_wrap() == FlexWrap::WrapReverse;
let aspect_ratio = style.aspect_ratio();
let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
let padding_border_sum = padding.sum_axes() + border.sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
let align_items = style.align_items().unwrap_or(AlignItems::Stretch);
let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
let justify_content = style.justify_content();
let layout_direction = style.direction();
let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width(),
_ => 0.0,
});
let mut content_box_inset = padding + border;
content_box_inset.bottom += scrollbar_gutter.y;
match layout_direction {
Direction::Ltr => content_box_inset.right += scrollbar_gutter.x,
Direction::Rtl => content_box_inset.left += scrollbar_gutter.x,
};
let node_outer_size = known_dimensions;
let node_inner_size = node_outer_size.maybe_sub(content_box_inset.sum_axes());
let gap = style.gap().resolve_or_zero(node_inner_size.or(Size::zero()), |val, basis| tree.calc(val, basis));
let container_size = Size::zero();
let inner_container_size = Size::zero();
AlgoConstants {
dir,
layout_direction,
is_row,
is_column,
is_wrap,
is_wrap_reverse,
min_size: style
.min_size()
.maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
max_size: style
.max_size()
.maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
margin,
border,
gap,
content_box_inset,
scrollbar_gutter,
align_items,
align_content,
justify_content,
node_outer_size,
node_inner_size,
container_size,
inner_container_size,
}
}
#[inline]
fn generate_anonymous_flex_items(
tree: &impl LayoutFlexboxContainer,
node: NodeId,
constants: &AlgoConstants,
) -> Vec<FlexItem> {
tree.child_ids(node)
.enumerate()
.map(|(index, child)| (index, child, tree.get_flexbox_child_style(child)))
.filter(|(_, _, style)| style.position() != Position::Absolute)
.filter(|(_, _, style)| style.box_generation_mode() != BoxGenerationMode::None)
.map(|(index, child, child_style)| {
let aspect_ratio = child_style.aspect_ratio();
let padding = child_style
.padding()
.resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis));
let border = child_style
.border()
.resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis));
let pb_sum = (padding + border).sum_axes();
let box_sizing_adjustment =
if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
FlexItem {
node: child,
order: index as u32,
size: child_style
.size()
.maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
min_size: child_style
.min_size()
.maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
max_size: child_style
.max_size()
.maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
inset: child_style
.inset()
.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s, |val, basis| tree.calc(val, basis))),
margin: child_style
.margin()
.resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
margin_is_auto: child_style.margin().map(LengthPercentageAuto::is_auto),
padding: child_style
.padding()
.resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
border: child_style
.border()
.resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
align_self: child_style.align_self().unwrap_or(constants.align_items),
overflow: child_style.overflow(),
scrollbar_width: child_style.scrollbar_width(),
flex_grow: child_style.flex_grow(),
flex_shrink: child_style.flex_shrink(),
flex_basis: 0.0,
inner_flex_basis: 0.0,
violation: 0.0,
frozen: false,
resolved_minimum_main_size: 0.0,
hypothetical_inner_size: Size::zero(),
hypothetical_outer_size: Size::zero(),
target_size: Size::zero(),
outer_target_size: Size::zero(),
content_flex_fraction: 0.0,
baseline: 0.0,
offset_main: 0.0,
offset_cross: 0.0,
}
})
.collect()
}
#[inline]
#[must_use]
fn determine_available_space(
known_dimensions: Size<Option<f32>>,
outer_available_space: Size<AvailableSpace>,
constants: &AlgoConstants,
) -> Size<AvailableSpace> {
let width = match known_dimensions.width {
Some(node_width) => AvailableSpace::Definite(node_width - constants.content_box_inset.horizontal_axis_sum()),
None => outer_available_space
.width
.maybe_sub(constants.margin.horizontal_axis_sum())
.maybe_sub(constants.content_box_inset.horizontal_axis_sum()),
};
let height = match known_dimensions.height {
Some(node_height) => AvailableSpace::Definite(node_height - constants.content_box_inset.vertical_axis_sum()),
None => outer_available_space
.height
.maybe_sub(constants.margin.vertical_axis_sum())
.maybe_sub(constants.content_box_inset.vertical_axis_sum()),
};
Size { width, height }
}
#[inline]
fn determine_flex_base_size(
tree: &mut impl LayoutFlexboxContainer,
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
flex_items: &mut [FlexItem],
) {
let dir = constants.dir;
for child in flex_items.iter_mut() {
let child_style = tree.get_flexbox_child_style(child.node);
let cross_axis_parent_size = constants.node_inner_size.cross(dir);
let child_parent_size = Size::from_cross(dir, cross_axis_parent_size);
let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
let child_min_cross = child.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
let child_max_cross = child.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
let cross_axis_available_space: AvailableSpace = match available_space.cross(dir) {
AvailableSpace::Definite(val) => AvailableSpace::Definite(
cross_axis_parent_size.unwrap_or(val).maybe_clamp(child_min_cross, child_max_cross),
),
AvailableSpace::MinContent => match child_min_cross {
Some(min) => AvailableSpace::Definite(min),
None => AvailableSpace::MinContent,
},
AvailableSpace::MaxContent => match child_max_cross {
Some(max) => AvailableSpace::Definite(max),
None => AvailableSpace::MaxContent,
},
};
let child_known_dimensions = {
let mut ckd = child.size.with_main(dir, None);
if child.align_self == AlignSelf::Stretch
&& !child.margin_is_auto.cross_start(constants.dir)
&& !child.margin_is_auto.cross_end(constants.dir)
&& ckd.cross(dir).is_none()
{
ckd.set_cross(
dir,
cross_axis_available_space.into_option().maybe_sub(child.margin.cross_axis_sum(dir)),
);
}
ckd
};
let container_width = constants.node_inner_size.main(dir);
let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox {
let padding = child_style.padding().resolve_or_zero(container_width, |val, basis| tree.calc(val, basis));
let border = child_style.border().resolve_or_zero(container_width, |val, basis| tree.calc(val, basis));
(padding + border).sum_axes()
} else {
Size::ZERO
}
.main(dir);
let flex_basis = child_style
.flex_basis()
.maybe_resolve(container_width, |val, basis| tree.calc(val, basis))
.maybe_add(box_sizing_adjustment);
drop(child_style);
child.flex_basis = 'flex_basis: {
let main_size = child.size.main(dir);
if let Some(flex_basis) = flex_basis.or(main_size) {
break 'flex_basis flex_basis;
};
let child_available_space = Size::MAX_CONTENT
.with_main(
dir,
if available_space.main(dir) == AvailableSpace::MinContent {
AvailableSpace::MinContent
} else {
AvailableSpace::MaxContent
},
)
.with_cross(dir, cross_axis_available_space);
debug_log!("COMPUTE CHILD BASE SIZE:");
break 'flex_basis tree.measure_child_size(
child.node,
child_known_dimensions,
child_parent_size,
child_available_space,
SizingMode::ContentSize,
dir.main_axis(),
Line::FALSE,
);
};
let padding_border_sum = child.padding.main_axis_sum(constants.dir) + child.border.main_axis_sum(constants.dir);
child.flex_basis = child.flex_basis.max(padding_border_sum);
child.inner_flex_basis =
child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
let padding_border_axes_sums = (child.padding + child.border).sum_axes().map(Some);
let style_min_main_size =
child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);
child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
let min_content_main_size = {
let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
debug_log!("COMPUTE CHILD MIN SIZE:");
tree.measure_child_size(
child.node,
child_known_dimensions,
child_parent_size,
child_available_space,
SizingMode::ContentSize,
dir.main_axis(),
Line::FALSE,
)
};
let clamped_min_content_size =
min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
});
let hypothetical_inner_min_main =
child.resolved_minimum_main_size.maybe_max(padding_border_axes_sums.main(constants.dir));
let hypothetical_inner_size =
child.flex_basis.maybe_clamp(Some(hypothetical_inner_min_main), child.max_size.main(constants.dir));
let hypothetical_outer_size = hypothetical_inner_size + child.margin.main_axis_sum(constants.dir);
child.hypothetical_inner_size.set_main(constants.dir, hypothetical_inner_size);
child.hypothetical_outer_size.set_main(constants.dir, hypothetical_outer_size);
}
}
#[inline]
fn collect_flex_lines<'a>(
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
flex_items: &'a mut Vec<FlexItem>,
) -> Vec<FlexLine<'a>> {
if !constants.is_wrap {
let mut lines = new_vec_with_capacity(1);
lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
lines
} else {
let main_axis_available_space = match constants.max_size.main(constants.dir) {
Some(max_size) => AvailableSpace::Definite(
available_space
.main(constants.dir)
.into_option()
.unwrap_or(max_size)
.maybe_max(constants.min_size.main(constants.dir)),
),
None => available_space.main(constants.dir),
};
match main_axis_available_space {
AvailableSpace::MaxContent => {
let mut lines = new_vec_with_capacity(1);
lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
lines
}
AvailableSpace::MinContent => {
let mut lines = new_vec_with_capacity(flex_items.len());
let mut items = &mut flex_items[..];
while !items.is_empty() {
let (line_items, rest) = items.split_at_mut(1);
lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
items = rest;
}
lines
}
AvailableSpace::Definite(main_axis_available_space) => {
let mut lines = new_vec_with_capacity(1);
let mut flex_items = &mut flex_items[..];
let main_axis_gap = constants.gap.main(constants.dir);
while !flex_items.is_empty() {
let mut line_length = 0.0;
let index = flex_items
.iter()
.enumerate()
.find(|&(idx, child)| {
let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
line_length > main_axis_available_space && idx != 0
})
.map(|(idx, _)| idx)
.unwrap_or(flex_items.len());
let (items, rest) = flex_items.split_at_mut(index);
lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
flex_items = rest;
}
lines
}
}
}
}
fn determine_container_main_size(
tree: &mut impl LayoutFlexboxContainer,
available_space: Size<AvailableSpace>,
lines: &mut [FlexLine<'_>],
constants: &mut AlgoConstants,
) {
let dir = constants.dir;
let main_content_box_inset = constants.content_box_inset.main_axis_sum(constants.dir);
let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
match available_space.main(dir) {
AvailableSpace::Definite(main_axis_available_space) => {
let longest_line_length: f32 = lines
.iter()
.map(|line| {
let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
let total_target_size = line
.items
.iter()
.map(|child| {
let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
(child.flex_basis.maybe_max(child.min_size.main(constants.dir))
+ child.margin.main_axis_sum(constants.dir))
.max(padding_border_sum)
})
.sum::<f32>();
total_target_size + line_main_axis_gap
})
.max_by(|a, b| a.total_cmp(b))
.unwrap_or(0.0);
let size = longest_line_length + main_content_box_inset;
if lines.len() > 1 {
f32_max(size, main_axis_available_space)
} else {
size
}
}
AvailableSpace::MinContent if constants.is_wrap => {
let longest_line_length: f32 = lines
.iter()
.map(|line| {
let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
let total_target_size = line
.items
.iter()
.map(|child| {
let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
(child.flex_basis.maybe_max(child.min_size.main(constants.dir))
+ child.margin.main_axis_sum(constants.dir))
.max(padding_border_sum)
})
.sum::<f32>();
total_target_size + line_main_axis_gap
})
.max_by(|a, b| a.total_cmp(b))
.unwrap_or(0.0);
longest_line_length + main_content_box_inset
}
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
let mut main_size = 0.0;
for line in lines.iter_mut() {
for item in line.items.iter_mut() {
let style_min = item.min_size.main(constants.dir);
let style_preferred = item.size.main(constants.dir);
let style_max = item.max_size.main(constants.dir);
let clamping_basis = Some(item.flex_basis).maybe_max(style_preferred);
let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);
let min_main_size = style_min
.maybe_max(flex_basis_min)
.or(flex_basis_min)
.unwrap_or(item.resolved_minimum_main_size)
.max(item.resolved_minimum_main_size);
let max_main_size =
style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);
let content_contribution = match (min_main_size, style_preferred, max_main_size) {
(min, Some(pref), max) if max <= min || max <= pref => {
pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
}
(min, _, max) if max <= min => min + item.margin.main_axis_sum(constants.dir),
_ if item.is_scroll_container() => {
item.flex_basis + item.margin.main_axis_sum(constants.dir)
}
_ => {
let cross_axis_parent_size = constants.node_inner_size.cross(dir);
let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
let child_min_cross = item.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
let child_max_cross = item.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
let cross_axis_available_space: AvailableSpace = available_space
.cross(dir)
.map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
.maybe_clamp(child_min_cross, child_max_cross);
let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
let child_known_dimensions = {
let mut ckd = item.size.with_main(dir, None);
if item.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
ckd.set_cross(
dir,
cross_axis_available_space
.into_option()
.maybe_sub(item.margin.cross_axis_sum(dir)),
);
}
ckd
};
debug_log!("COMPUTE CHILD BASE SIZE (for intrinsic main size):");
let content_main_size = tree.measure_child_size(
item.node,
child_known_dimensions,
constants.node_inner_size,
child_available_space,
SizingMode::InherentSize,
dir.main_axis(),
Line::FALSE,
) + item.margin.main_axis_sum(constants.dir);
if constants.is_row {
content_main_size.maybe_clamp(style_min, style_max).max(main_content_box_inset)
} else {
content_main_size
.max(item.flex_basis)
.maybe_clamp(style_min, style_max)
.max(main_content_box_inset)
}
}
};
item.content_flex_fraction = {
let diff = content_contribution - item.flex_basis;
if diff > 0.0 {
diff / f32_max(1.0, item.flex_grow)
} else if diff < 0.0 {
let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
diff / scaled_shrink_factor
} else {
0.0
}
};
}
let item_main_size_sum = line
.items
.iter_mut()
.map(|item| {
let flex_fraction = item.content_flex_fraction;
let flex_contribution = if item.content_flex_fraction > 0.0 {
f32_max(1.0, item.flex_grow) * flex_fraction
} else if item.content_flex_fraction < 0.0 {
let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
scaled_shrink_factor * flex_fraction
} else {
0.0
};
let size = item.flex_basis + flex_contribution;
item.outer_target_size.set_main(constants.dir, size);
item.target_size.set_main(constants.dir, size);
size
})
.sum::<f32>();
let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
main_size = f32_max(main_size, item_main_size_sum + gap_sum)
}
main_size + main_content_box_inset
}
}
});
let outer_main_size = outer_main_size
.maybe_clamp(constants.min_size.main(constants.dir), constants.max_size.main(constants.dir))
.max(main_content_box_inset - constants.scrollbar_gutter.main(constants.dir));
let inner_main_size = f32_max(outer_main_size - main_content_box_inset, 0.0);
constants.container_size.set_main(constants.dir, outer_main_size);
constants.inner_container_size.set_main(constants.dir, inner_main_size);
constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
}
#[inline]
fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants) {
let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
let total_hypothetical_outer_main_size =
line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
let used_flex_factor: f32 = total_main_axis_gap + total_hypothetical_outer_main_size;
let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
let shrinking = used_flex_factor > constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
let exactly_sized = !growing & !shrinking;
for child in line.items.iter_mut() {
let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
child.target_size.set_main(constants.dir, inner_target_size);
if exactly_sized
|| (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
|| (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
|| (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
{
child.frozen = true;
let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
child.outer_target_size.set_main(constants.dir, outer_target_size);
}
}
if exactly_sized {
return;
}
let used_space: f32 = total_main_axis_gap
+ line
.items
.iter()
.map(|child| {
if child.frozen {
child.outer_target_size.main(constants.dir)
} else {
child.flex_basis + child.margin.main_axis_sum(constants.dir)
}
})
.sum::<f32>();
let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
loop {
if line.items.iter().all(|child| child.frozen) {
break;
}
let used_space: f32 = total_main_axis_gap
+ line
.items
.iter()
.map(|child| {
if child.frozen {
child.outer_target_size.main(constants.dir)
} else {
child.flex_basis + child.margin.main_axis_sum(constants.dir)
}
})
.sum::<f32>();
let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
(flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
});
let free_space = if growing && sum_flex_grow < 1.0 {
(initial_free_space * sum_flex_grow - total_main_axis_gap)
.maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
} else if shrinking && sum_flex_shrink < 1.0 {
(initial_free_space * sum_flex_shrink - total_main_axis_gap)
.maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
} else {
(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
.unwrap_or(used_flex_factor - used_space)
};
if free_space.is_normal() {
if growing && sum_flex_grow > 0.0 {
for child in &mut unfrozen {
child
.target_size
.set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
}
} else if shrinking && sum_flex_shrink > 0.0 {
let sum_scaled_shrink_factor: f32 =
unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
if sum_scaled_shrink_factor > 0.0 {
for child in &mut unfrozen {
let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
child.target_size.set_main(
constants.dir,
child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
)
}
}
}
}
let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
let max_main = child.max_size.main(constants.dir);
let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
child.violation = clamped - child.target_size.main(constants.dir);
child.target_size.set_main(constants.dir, clamped);
child.outer_target_size.set_main(
constants.dir,
child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
);
acc + child.violation
});
for child in &mut unfrozen {
match total_violation {
v if v > 0.0 => child.frozen = child.violation > 0.0,
v if v < 0.0 => child.frozen = child.violation < 0.0,
_ => child.frozen = true,
}
}
}
}
#[inline]
fn determine_hypothetical_cross_size(
tree: &mut impl LayoutFlexboxContainer,
line: &mut FlexLine,
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
) {
for child in line.items.iter_mut() {
let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
let child_known_main = constants.container_size.main(constants.dir).into();
let child_cross = child
.size
.cross(constants.dir)
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
.maybe_max(padding_border_sum);
let child_available_cross = available_space
.cross(constants.dir)
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
.maybe_max(padding_border_sum);
let child_inner_cross = child_cross.unwrap_or_else(|| {
tree.measure_child_size(
child.node,
Size {
width: if constants.is_row { child.target_size.width.into() } else { child_cross },
height: if constants.is_row { child_cross } else { child.target_size.height.into() },
},
constants.node_inner_size,
Size {
width: if constants.is_row { child_known_main } else { child_available_cross },
height: if constants.is_row { child_available_cross } else { child_known_main },
},
SizingMode::ContentSize,
constants.dir.cross_axis(),
Line::FALSE,
)
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
.max(padding_border_sum)
});
let child_outer_cross = child_inner_cross + child.margin.cross_axis_sum(constants.dir);
child.hypothetical_inner_size.set_cross(constants.dir, child_inner_cross);
child.hypothetical_outer_size.set_cross(constants.dir, child_outer_cross);
}
}
#[inline]
fn calculate_children_base_lines(
tree: &mut impl LayoutFlexboxContainer,
node_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
flex_lines: &mut [FlexLine],
constants: &AlgoConstants,
) {
if !constants.is_row {
return;
}
for line in flex_lines {
let line_baseline_child_count =
line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
if line_baseline_child_count <= 1 {
continue;
}
for child in line.items.iter_mut() {
if child.align_self != AlignSelf::Baseline {
continue;
}
let measured_size_and_baselines = tree.perform_child_layout(
child.node,
Size {
width: if constants.is_row {
child.target_size.width.into()
} else {
child.hypothetical_inner_size.width.into()
},
height: if constants.is_row {
child.hypothetical_inner_size.height.into()
} else {
child.target_size.height.into()
},
},
constants.node_inner_size,
Size {
width: if constants.is_row {
constants.container_size.width.into()
} else {
available_space.width.maybe_set(node_size.width)
},
height: if constants.is_row {
available_space.height.maybe_set(node_size.height)
} else {
constants.container_size.height.into()
},
},
SizingMode::ContentSize,
Line::FALSE,
);
let baseline = measured_size_and_baselines.first_baselines.y;
let height = measured_size_and_baselines.size.height;
child.baseline = baseline.unwrap_or(height) + child.margin.top;
}
}
}
#[inline]
fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
if !constants.is_wrap && node_size.cross(constants.dir).is_some() {
let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
let cross_min_size = constants.min_size.cross(constants.dir);
let cross_max_size = constants.max_size.cross(constants.dir);
flex_lines[0].cross_size = node_size
.cross(constants.dir)
.maybe_clamp(cross_min_size, cross_max_size)
.maybe_sub(cross_axis_padding_border)
.maybe_max(0.0)
.unwrap_or(0.0);
} else {
for line in flex_lines.iter_mut() {
let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
line.cross_size = line
.items
.iter()
.map(|child| {
if child.align_self == AlignSelf::Baseline
&& !child.margin_is_auto.cross_start(constants.dir)
&& !child.margin_is_auto.cross_end(constants.dir)
{
max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
} else {
child.hypothetical_outer_size.cross(constants.dir)
}
})
.fold(0.0, |acc, x| acc.max(x));
}
if !constants.is_wrap {
let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
let cross_min_size = constants.min_size.cross(constants.dir);
let cross_max_size = constants.max_size.cross(constants.dir);
flex_lines[0].cross_size = flex_lines[0].cross_size.maybe_clamp(
cross_min_size.maybe_sub(cross_axis_padding_border),
cross_max_size.maybe_sub(cross_axis_padding_border),
);
}
}
}
#[inline]
fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
if constants.align_content == AlignContent::Stretch {
let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
let cross_min_size = constants.min_size.cross(constants.dir);
let cross_max_size = constants.max_size.cross(constants.dir);
let container_min_inner_cross = node_size
.cross(constants.dir)
.or(cross_min_size)
.maybe_clamp(cross_min_size, cross_max_size)
.maybe_sub(cross_axis_padding_border)
.maybe_max(0.0)
.unwrap_or(0.0);
let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
let lines_total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
if lines_total_cross < container_min_inner_cross {
let remaining = container_min_inner_cross - lines_total_cross;
let addition = remaining / flex_lines.len() as f32;
flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
}
}
}
#[inline]
fn determine_used_cross_size(
tree: &impl LayoutFlexboxContainer,
flex_lines: &mut [FlexLine],
constants: &AlgoConstants,
) {
for line in flex_lines {
let line_cross_size = line.cross_size;
for child in line.items.iter_mut() {
let child_style = tree.get_flexbox_child_style(child.node);
child.target_size.set_cross(
constants.dir,
if child.align_self == AlignSelf::Stretch
&& !child.margin_is_auto.cross_start(constants.dir)
&& !child.margin_is_auto.cross_end(constants.dir)
&& child_style.size().cross(constants.dir).is_auto()
{
let padding = child_style
.padding()
.resolve_or_zero(constants.node_inner_size, |val, basis| tree.calc(val, basis));
let border = child_style
.border()
.resolve_or_zero(constants.node_inner_size, |val, basis| tree.calc(val, basis));
let pb_sum = (padding + border).sum_axes();
let box_sizing_adjustment =
if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
let max_size_ignoring_aspect_ratio = child_style
.max_size()
.maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
.maybe_add(box_sizing_adjustment);
(line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
child.min_size.cross(constants.dir),
max_size_ignoring_aspect_ratio.cross(constants.dir),
)
} else {
child.hypothetical_inner_size.cross(constants.dir)
},
);
child.outer_target_size.set_cross(
constants.dir,
child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
);
}
}
}
#[inline]
fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
for line in flex_lines {
let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
let used_space: f32 = total_main_axis_gap
+ line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
let free_space = constants.inner_container_size.main(constants.dir) - used_space;
let mut num_auto_margins = 0;
for child in line.items.iter_mut() {
if child.margin_is_auto.main_start(constants.dir) {
num_auto_margins += 1;
}
if child.margin_is_auto.main_end(constants.dir) {
num_auto_margins += 1;
}
}
if free_space > 0.0 && num_auto_margins > 0 {
let margin = free_space / num_auto_margins as f32;
for child in line.items.iter_mut() {
if child.margin_is_auto.main_start(constants.dir) {
if constants.is_row {
child.margin.left = margin;
} else {
child.margin.top = margin;
}
}
if child.margin_is_auto.main_end(constants.dir) {
if constants.is_row {
child.margin.right = margin;
} else {
child.margin.bottom = margin;
}
}
}
}
let num_items = line.items.len();
let layout_reverse = constants.dir.is_reverse();
let gap = constants.gap.main(constants.dir);
let is_safe = false; let raw_justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
let justify_content_mode = apply_alignment_fallback(free_space, num_items, raw_justify_content_mode, is_safe);
let justify_item = |(i, child): (usize, &mut FlexItem)| {
child.offset_main =
compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
};
if layout_reverse {
line.items.iter_mut().rev().enumerate().for_each(justify_item);
} else {
line.items.iter_mut().enumerate().for_each(justify_item);
}
}
}
#[inline]
fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
for line in flex_lines {
let line_cross_size = line.cross_size;
let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
for child in line.items.iter_mut() {
let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
if constants.is_row {
child.margin.top = free_space / 2.0;
child.margin.bottom = free_space / 2.0;
} else {
child.margin.left = free_space / 2.0;
child.margin.right = free_space / 2.0;
}
} else if child.margin_is_auto.cross_start(constants.dir) {
if constants.is_row {
child.margin.top = free_space;
} else {
child.margin.left = free_space;
}
} else if child.margin_is_auto.cross_end(constants.dir) {
if constants.is_row {
child.margin.bottom = free_space;
} else {
child.margin.right = free_space;
}
} else {
child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
}
}
}
}
#[inline]
fn align_flex_items_along_cross_axis(
child: &FlexItem,
free_space: f32,
max_baseline: f32,
constants: &AlgoConstants,
) -> f32 {
let cross_axis_should_reverse = constants.is_column && matches!(constants.layout_direction, Direction::Rtl);
match child.align_self {
AlignSelf::Start => {
if cross_axis_should_reverse {
free_space
} else {
0.0
}
}
AlignSelf::FlexStart => {
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
free_space
} else {
0.0
}
}
AlignSelf::End => {
if cross_axis_should_reverse {
0.0
} else {
free_space
}
}
AlignSelf::FlexEnd => {
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
0.0
} else {
free_space
}
}
AlignSelf::Center => free_space / 2.0,
AlignSelf::Baseline => {
if constants.is_row {
max_baseline - child.baseline
} else {
let baseline_column_should_reverse = cross_axis_should_reverse && !constants.is_wrap;
if constants.is_wrap_reverse ^ baseline_column_should_reverse {
free_space
} else {
0.0
}
}
}
AlignSelf::Stretch => {
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
free_space
} else {
0.0
}
}
}
}
#[inline]
#[must_use]
fn determine_container_cross_size(
flex_lines: &[FlexLine],
node_size: Size<Option<f32>>,
constants: &mut AlgoConstants,
) -> f32 {
let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
let padding_border_sum = constants.content_box_inset.cross_axis_sum(constants.dir);
let cross_scrollbar_gutter = constants.scrollbar_gutter.cross(constants.dir);
let min_cross_size = constants.min_size.cross(constants.dir);
let max_cross_size = constants.max_size.cross(constants.dir);
let outer_container_size = node_size
.cross(constants.dir)
.unwrap_or(total_line_cross_size + total_cross_axis_gap + padding_border_sum)
.maybe_clamp(min_cross_size, max_cross_size)
.max(padding_border_sum - cross_scrollbar_gutter);
let inner_container_size = f32_max(outer_container_size - padding_border_sum, 0.0);
constants.container_size.set_cross(constants.dir, outer_container_size);
constants.inner_container_size.set_cross(constants.dir, inner_container_size);
total_line_cross_size
}
#[inline]
fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
let num_lines = flex_lines.len();
let gap = constants.gap.cross(constants.dir);
let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
let is_safe = false;
let align_content_mode = apply_alignment_fallback(free_space, num_lines, constants.align_content, is_safe);
let align_line = |(i, line): (usize, &mut FlexLine)| {
line.offset_cross =
compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
};
if constants.is_wrap_reverse {
flex_lines.iter_mut().rev().enumerate().for_each(align_line);
} else {
flex_lines.iter_mut().enumerate().for_each(align_line);
}
}
#[allow(clippy::too_many_arguments)]
fn calculate_flex_item(
tree: &mut impl LayoutFlexboxContainer,
item: &mut FlexItem,
total_offset_main: &mut f32,
total_offset_cross: f32,
line_offset_cross: f32,
#[cfg(feature = "content_size")] total_content_size: &mut Size<f32>,
container_size: Size<f32>,
node_inner_size: Size<Option<f32>>,
direction: FlexDirection,
layout_direction: Direction,
) {
let layout_output = tree.perform_child_layout(
item.node,
item.target_size.map(|s| s.into()),
node_inner_size,
container_size.map(|s| s.into()),
SizingMode::ContentSize,
Line::FALSE,
);
let LayoutOutput {
size,
#[cfg(feature = "content_size")]
content_size,
..
} = layout_output;
let is_rtl_row = direction.is_row() && layout_direction.is_rtl();
let is_rtl_column = direction.is_column() && layout_direction.is_rtl();
let main_relative_inset = if is_rtl_row {
item.inset.main_end(direction).or(item.inset.main_start(direction).map(|pos| -pos)).unwrap_or(0.0)
} else {
item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0)
};
let cross_relative_inset = if is_rtl_column {
item.inset.cross_end(direction).map(|pos| -pos).or(item.inset.cross_start(direction)).unwrap_or(0.0)
} else {
item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0)
};
let effective_line_offset_cross = if is_rtl_column { 0.0 } else { line_offset_cross };
let offset_main = if is_rtl_row {
*total_offset_main - item.offset_main - item.margin.main_end(direction) - main_relative_inset - size.width
} else {
*total_offset_main + item.offset_main + item.margin.main_start(direction) + main_relative_inset
};
let offset_cross = total_offset_cross
+ item.offset_cross
+ effective_line_offset_cross
+ item.margin.cross_start(direction)
+ cross_relative_inset;
if direction.is_row() {
let baseline_offset_cross =
total_offset_cross + item.offset_cross + effective_line_offset_cross + item.margin.cross_start(direction);
let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
item.baseline = baseline_offset_cross + inner_baseline;
} else {
let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
item.baseline = baseline_offset_main + inner_baseline;
}
let location = if direction.is_row() {
Point { x: offset_main, y: offset_cross }
} else {
Point { x: offset_cross, y: offset_main }
};
let scrollbar_size = Size {
width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
};
tree.set_unrounded_layout(
item.node,
&Layout {
order: item.order,
size,
#[cfg(feature = "content_size")]
content_size,
scrollbar_size,
location,
padding: item.padding,
border: item.border,
margin: item.margin,
},
);
if is_rtl_row {
*total_offset_main -= item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
} else {
*total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
}
#[cfg(feature = "content_size")]
{
let contribution_location = if layout_direction.is_rtl() {
Point { x: container_size.width - (location.x + size.width), y: location.y }
} else {
location
};
*total_content_size = total_content_size.f32_max(compute_content_size_contribution(
contribution_location,
size,
content_size,
item.overflow,
));
}
}
#[allow(clippy::too_many_arguments)]
fn calculate_layout_line(
tree: &mut impl LayoutFlexboxContainer,
line: &mut FlexLine,
total_offset_cross: &mut f32,
#[cfg(feature = "content_size")] content_size: &mut Size<f32>,
container_size: Size<f32>,
node_inner_size: Size<Option<f32>>,
padding_border: Rect<f32>,
direction: FlexDirection,
layout_direction: Direction,
) {
let mut total_offset_main = if layout_direction.is_rtl() && direction.is_row() {
container_size.width - padding_border.main_end(direction)
} else {
padding_border.main_start(direction)
};
let line_offset_cross = line.offset_cross;
let is_rtl_column = layout_direction.is_rtl() && direction.is_column();
if is_rtl_column {
*total_offset_cross -= line_offset_cross + line.cross_size;
}
if direction.is_reverse() {
for item in line.items.iter_mut().rev() {
calculate_flex_item(
tree,
item,
&mut total_offset_main,
*total_offset_cross,
line_offset_cross,
#[cfg(feature = "content_size")]
content_size,
container_size,
node_inner_size,
direction,
layout_direction,
);
}
} else {
for item in line.items.iter_mut() {
calculate_flex_item(
tree,
item,
&mut total_offset_main,
*total_offset_cross,
line_offset_cross,
#[cfg(feature = "content_size")]
content_size,
container_size,
node_inner_size,
direction,
layout_direction,
);
}
}
if !is_rtl_column {
*total_offset_cross += line_offset_cross + line.cross_size;
}
}
#[inline]
fn final_layout_pass(
tree: &mut impl LayoutFlexboxContainer,
flex_lines: &mut [FlexLine],
constants: &AlgoConstants,
) -> Size<f32> {
let mut total_offset_cross = if constants.is_column && constants.layout_direction.is_rtl() {
constants.container_size.width - constants.content_box_inset.cross_end(constants.dir)
} else {
constants.content_box_inset.cross_start(constants.dir)
};
#[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
let mut content_size = Size::ZERO;
if constants.is_wrap_reverse {
for line in flex_lines.iter_mut().rev() {
calculate_layout_line(
tree,
line,
&mut total_offset_cross,
#[cfg(feature = "content_size")]
&mut content_size,
constants.container_size,
constants.node_inner_size,
constants.content_box_inset,
constants.dir,
constants.layout_direction,
);
}
} else {
for line in flex_lines.iter_mut() {
calculate_layout_line(
tree,
line,
&mut total_offset_cross,
#[cfg(feature = "content_size")]
&mut content_size,
constants.container_size,
constants.node_inner_size,
constants.content_box_inset,
constants.dir,
constants.layout_direction,
);
}
}
content_size.width += if constants.layout_direction.is_rtl() {
constants.content_box_inset.left - constants.border.left - constants.scrollbar_gutter.x
} else {
constants.content_box_inset.right - constants.border.right - constants.scrollbar_gutter.x
};
content_size.height += constants.content_box_inset.bottom - constants.border.bottom - constants.scrollbar_gutter.y;
content_size
}
#[inline]
fn perform_absolute_layout_on_absolute_children(
tree: &mut impl LayoutFlexboxContainer,
node: NodeId,
constants: &AlgoConstants,
) -> Size<f32> {
let container_width = constants.container_size.width;
let container_height = constants.container_size.height;
let inset_relative_size =
constants.container_size - constants.border.sum_axes() - constants.scrollbar_gutter.into();
#[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
let mut content_size = Size::ZERO;
for order in 0..tree.child_count(node) {
let child = tree.get_child_id(node, order);
let child_style = tree.get_flexbox_child_style(child);
if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
{
continue;
}
let overflow = child_style.overflow();
let scrollbar_width = child_style.scrollbar_width();
let aspect_ratio = child_style.aspect_ratio();
let align_self = child_style.align_self().unwrap_or(constants.align_items);
let margin = child_style
.margin()
.map(|margin| margin.resolve_to_option(inset_relative_size.width, |val, basis| tree.calc(val, basis)));
let padding =
child_style.padding().resolve_or_zero(Some(inset_relative_size.width), |val, basis| tree.calc(val, basis));
let border =
child_style.border().resolve_or_zero(Some(inset_relative_size.width), |val, basis| tree.calc(val, basis));
let padding_border_sum = (padding + border).sum_axes();
let box_sizing_adjustment =
if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
let left =
child_style.inset().left.maybe_resolve(inset_relative_size.width, |val, basis| tree.calc(val, basis));
let right =
child_style.inset().right.maybe_resolve(inset_relative_size.width, |val, basis| tree.calc(val, basis));
let top = child_style.inset().top.maybe_resolve(inset_relative_size.height, |val, basis| tree.calc(val, basis));
let bottom =
child_style.inset().bottom.maybe_resolve(inset_relative_size.height, |val, basis| tree.calc(val, basis));
let style_size = child_style
.size()
.maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let min_size = child_style
.min_size()
.maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment)
.or(padding_border_sum.map(Some))
.maybe_max(padding_border_sum);
let max_size = child_style
.max_size()
.maybe_resolve(inset_relative_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
drop(child_style);
if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
let new_width_raw = inset_relative_size.width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
}
if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
let new_height_raw =
inset_relative_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
}
let measured_size = tree.measure_child_size_both(
child,
known_dimensions,
constants.node_inner_size,
Size {
width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
},
SizingMode::InherentSize,
Line::FALSE,
);
let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
let layout_output = tree.perform_child_layout(
child,
final_size.map(Some),
constants.node_inner_size,
Size {
width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
},
SizingMode::InherentSize,
Line::FALSE,
);
let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
let free_space = Size {
width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
}
.f32_max(Size::ZERO);
let resolved_margin = {
let auto_margin_size = Size {
width: {
let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
if auto_margin_count > 0 {
free_space.width / auto_margin_count as f32
} else {
0.0
}
},
height: {
let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
if auto_margin_count > 0 {
free_space.height / auto_margin_count as f32
} else {
0.0
}
},
};
Rect {
left: margin.left.unwrap_or(auto_margin_size.width),
right: margin.right.unwrap_or(auto_margin_size.width),
top: margin.top.unwrap_or(auto_margin_size.height),
bottom: margin.bottom.unwrap_or(auto_margin_size.height),
}
};
let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
let main_axis_is_horizontal = constants.is_row;
let cross_axis_is_horizontal = !constants.is_row;
let main_is_rtl = main_axis_is_horizontal && constants.layout_direction.is_rtl();
let cross_is_rtl = cross_axis_is_horizontal && constants.layout_direction.is_rtl();
let main_axis_flex_start_reversed = constants.dir.is_reverse() ^ main_is_rtl;
let cross_axis_flex_start_reversed = constants.is_wrap_reverse ^ cross_is_rtl;
let main_start_scrollbar_offset =
if main_is_rtl { constants.scrollbar_gutter.main(constants.dir) } else { 0.0 };
let cross_start_scrollbar_offset =
if cross_is_rtl { constants.scrollbar_gutter.cross(constants.dir) } else { 0.0 };
let main_end_scrollbar_offset = if main_is_rtl { 0.0 } else { constants.scrollbar_gutter.main(constants.dir) };
let cross_end_scrollbar_offset =
if cross_is_rtl { 0.0 } else { constants.scrollbar_gutter.cross(constants.dir) };
let offset_main = if start_main.is_some() || end_main.is_some() {
if main_is_rtl && end_main.is_some() {
constants.container_size.main(constants.dir)
- constants.border.main_end(constants.dir)
- main_end_scrollbar_offset
- final_size.main(constants.dir)
- end_main.unwrap_or(0.0)
- resolved_margin.main_end(constants.dir)
} else if let Some(start) = start_main {
start
+ constants.border.main_start(constants.dir)
+ main_start_scrollbar_offset
+ resolved_margin.main_start(constants.dir)
} else {
constants.container_size.main(constants.dir)
- constants.border.main_end(constants.dir)
- main_end_scrollbar_offset
- final_size.main(constants.dir)
- end_main.unwrap_or(0.0)
- resolved_margin.main_end(constants.dir)
}
} else {
match (constants.justify_content.unwrap_or(JustifyContent::Start), main_axis_flex_start_reversed) {
(JustifyContent::SpaceBetween, _)
| (JustifyContent::Stretch, false)
| (JustifyContent::FlexStart, false)
| (JustifyContent::FlexEnd, true) => {
constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
}
(JustifyContent::Start, false) => {
constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
}
(JustifyContent::Start, true) => {
constants.container_size.main(constants.dir)
- constants.content_box_inset.main_end(constants.dir)
- final_size.main(constants.dir)
- resolved_margin.main_end(constants.dir)
}
(JustifyContent::End, false) => {
constants.container_size.main(constants.dir)
- constants.content_box_inset.main_end(constants.dir)
- final_size.main(constants.dir)
- resolved_margin.main_end(constants.dir)
}
(JustifyContent::End, true) => {
constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
}
(JustifyContent::FlexEnd, false)
| (JustifyContent::FlexStart, true)
| (JustifyContent::Stretch, true) => {
constants.container_size.main(constants.dir)
- constants.content_box_inset.main_end(constants.dir)
- final_size.main(constants.dir)
- resolved_margin.main_end(constants.dir)
}
(JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
(constants.container_size.main(constants.dir)
+ constants.content_box_inset.main_start(constants.dir)
- constants.content_box_inset.main_end(constants.dir)
- final_size.main(constants.dir)
+ resolved_margin.main_start(constants.dir)
- resolved_margin.main_end(constants.dir))
/ 2.0
}
}
};
let offset_cross = if start_cross.is_some() || end_cross.is_some() {
if cross_is_rtl && end_cross.is_some() {
constants.container_size.cross(constants.dir)
- constants.border.cross_end(constants.dir)
- cross_end_scrollbar_offset
- final_size.cross(constants.dir)
- end_cross.unwrap_or(0.0)
- resolved_margin.cross_end(constants.dir)
} else if let Some(start) = start_cross {
start
+ constants.border.cross_start(constants.dir)
+ cross_start_scrollbar_offset
+ resolved_margin.cross_start(constants.dir)
} else {
constants.container_size.cross(constants.dir)
- constants.border.cross_end(constants.dir)
- cross_end_scrollbar_offset
- final_size.cross(constants.dir)
- end_cross.unwrap_or(0.0)
- resolved_margin.cross_end(constants.dir)
}
} else {
match (align_self, cross_axis_flex_start_reversed) {
(AlignSelf::Start, false) => {
constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
}
(AlignSelf::Start, true) => {
constants.container_size.cross(constants.dir)
- constants.content_box_inset.cross_end(constants.dir)
- final_size.cross(constants.dir)
- resolved_margin.cross_end(constants.dir)
}
(AlignSelf::End, false) => {
constants.container_size.cross(constants.dir)
- constants.content_box_inset.cross_end(constants.dir)
- final_size.cross(constants.dir)
- resolved_margin.cross_end(constants.dir)
}
(AlignSelf::End, true) => {
constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
}
(AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
| (AlignSelf::FlexEnd, true) => {
constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
}
(AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
| (AlignSelf::FlexEnd, false) => {
constants.container_size.cross(constants.dir)
- constants.content_box_inset.cross_end(constants.dir)
- final_size.cross(constants.dir)
- resolved_margin.cross_end(constants.dir)
}
(AlignSelf::Center, _) => {
(constants.container_size.cross(constants.dir)
+ constants.content_box_inset.cross_start(constants.dir)
- constants.content_box_inset.cross_end(constants.dir)
- final_size.cross(constants.dir)
+ resolved_margin.cross_start(constants.dir)
- resolved_margin.cross_end(constants.dir))
/ 2.0
}
}
};
let location = match constants.is_row {
true => Point { x: offset_main, y: offset_cross },
false => Point { x: offset_cross, y: offset_main },
};
let scrollbar_size = Size {
width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
};
tree.set_unrounded_layout(
child,
&Layout {
order: order as u32,
size: final_size,
#[cfg(feature = "content_size")]
content_size: layout_output.content_size,
scrollbar_size,
location,
padding,
border,
margin: resolved_margin,
},
);
#[cfg(feature = "content_size")]
{
let size_content_size_contribution = Size {
width: match overflow.x {
Overflow::Visible => f32_max(final_size.width, layout_output.content_size.width),
_ => final_size.width,
},
height: match overflow.y {
Overflow::Visible => f32_max(final_size.height, layout_output.content_size.height),
_ => final_size.height,
},
};
if size_content_size_contribution.has_non_zero_area() {
let absolute_area_offset = Point {
x: constants.border.left
+ if constants.layout_direction.is_rtl() { constants.scrollbar_gutter.x } else { 0.0 },
y: constants.border.top,
};
let relative_location =
Point { x: location.x - absolute_area_offset.x, y: location.y - absolute_area_offset.y };
let content_size_contribution = Size {
width: if constants.layout_direction.is_rtl() {
let overflow_extra_width =
f32_max(size_content_size_contribution.width - final_size.width, 0.0);
f32_max(inset_relative_size.width - relative_location.x, 0.0) + overflow_extra_width
} else {
relative_location.x + size_content_size_contribution.width
},
height: relative_location.y + size_content_size_contribution.height,
};
content_size = content_size.f32_max(content_size_contribution);
}
}
}
content_size
}
#[inline(always)]
fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
if num_items <= 1 {
0.0
} else {
gap * (num_items - 1) as f32
}
}