use crate::compute::common::alignment::{compute_alignment_offset, resolve_self_alignment_safety};
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{
AlignContent, AlignContentKeyword, AlignItems, AlignItemsKeyword, AlignSelf, AvailableSpace, Contain,
JustifyContent, LengthPercentageAuto, Overflow, Position,
};
use crate::style::{CoreStyle, FlexDirection, FlexboxContainerStyle, FlexboxItemStyle};
use crate::style_helpers::{TaffyMaxContent, TaffyMinContent};
use crate::tree::{
AxisStaticAlign, AxisStaticEdge, AxisStaticPosition, LayoutFlexboxContainer, LayoutPartialTreeExt, NodeId,
OofPositioningArea,
};
use crate::tree::{Baselines, Layout, LayoutInput, LayoutOutput, OofCandidate, OofCandidates, RunMode, SizingMode};
use crate::util::debug::debug_log;
use crate::util::sys::{f32_max, f32_min, new_vec_with_capacity, Vec};
use crate::util::MaybeMath;
use crate::util::{MaybeResolve, ResolveOrZero};
use crate::{BoxGenerationMode, BoxSizing, Dimension, Direction, RequestedAxis};
use super::common::alignment::apply_alignment_fallback;
#[cfg(feature = "content_size")]
use super::common::scrollable_overflow::compute_scrollable_overflow_contribution;
use super::common::sizing_keyword::{resolve_sizing_keyword, SizingKeywordResolution};
struct FlexItem {
node: NodeId,
order: u32,
size: Size<Option<f32>>,
size_style: Size<Dimension>,
min_size: Size<Option<f32>>,
max_size: Size<Option<f32>>,
aspect_ratio: Option<f32>,
align_self: AlignSelf,
overflow: Point<Overflow>,
contain: Contain,
scrollbar_width: f32,
flex_shrink: f32,
flex_grow: f32,
flex_basis_is_definite: bool,
resolved_minimum_main_size: f32,
relative_inset: Size<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,
oof_candidates: OofCandidates,
}
impl FlexItem {
fn is_scroll_container(&self) -> bool {
self.overflow.x.is_scroll_container() | self.overflow.y.is_scroll_container()
}
fn participates_in_baseline_alignment(&self, dir: FlexDirection) -> bool {
self.align_self == AlignSelf::BASELINE
&& !self.margin_is_auto.cross_start(dir)
&& !self.margin_is_auto.cross_end(dir)
}
}
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,
#[cfg(feature = "flexbox_balance")]
is_balance: bool,
#[cfg(feature = "flexbox_balance")]
line_count: Option<u16>,
min_size: Size<Option<f32>>,
max_size: Size<Option<f32>>,
border: Rect<f32>,
content_box_inset: Rect<f32>,
scrollbar_gutter: Point<f32>,
#[cfg(feature = "content_size")]
is_scroll_container: bool,
gap: Size<f32>,
align_items: AlignItems,
align_content: AlignContent,
justify_content: JustifyContent,
node_outer_size: Size<Option<f32>>,
node_inner_size: Size<Option<f32>>,
known_main_size_is_definite: bool,
has_definite_main_size: bool,
has_definite_cross_size: bool,
cross_axis_available_space_is_definite: bool,
container_size: Size<f32>,
inner_container_size: Size<f32>,
}
impl AlgoConstants {
#[inline]
fn divided_cross_space(&self, cross_available_space: f32) -> f32 {
#[cfg(feature = "flexbox_balance")]
if let Some(line_count) = self.line_count {
if line_count > 1 {
let line_count = line_count as f32;
return (cross_available_space - (line_count - 1.0) * self.gap.cross(self.dir)) / line_count;
}
}
cross_available_space
}
}
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 contain = style.contain();
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 });
}
if inputs.axis == RequestedAxis::Horizontal {
if let Some(width) = styled_based_known_dimensions.width {
return LayoutOutput::from_outer_size(Size { width, height: 0.0 });
}
}
}
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);
let known_dimensions_are_definite = inputs
.known_dimensions_are_definite
.zip_map(known_dimensions, |is_definite, known_dimension| is_definite || known_dimension.is_none());
let mut output = compute_preliminary(
tree,
node,
LayoutInput { known_dimensions: styled_based_known_dimensions, known_dimensions_are_definite, ..inputs },
);
if contain.suppresses_baseline() {
output.baselines = Baselines::NONE;
}
output
}
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,
inputs.known_dimensions_are_definite,
parent_size,
available_space,
);
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");
#[cfg(feature = "flexbox_balance")]
let mut flex_lines = if constants.is_balance {
collect_balanced_flex_lines(&constants, available_space, &mut flex_items)
} else {
collect_flex_lines(&constants, available_space, &mut flex_items)
};
#[cfg(not(feature = "flexbox_balance"))]
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);
}
if run_mode == RunMode::ComputeSize {
let main_axis_is_requested_axis = match inputs.axis {
RequestedAxis::Horizontal => constants.dir.is_row(),
RequestedAxis::Vertical => constants.dir.is_column(),
RequestedAxis::Both => false,
};
if main_axis_is_requested_axis {
let size = Size::ZERO.with_main(constants.dir, constants.container_size.main(constants.dir));
return LayoutOutput::from_outer_size(size);
}
}
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_overflow_rect = final_layout_pass(tree, &mut flex_lines, &constants);
let mut candidates = OofCandidates::new();
collect_oof_candidates(tree, node, &constants, &mut flex_lines, &mut candidates);
let absolute_position_inset = constants.border
+ Rect {
left: if constants.layout_direction.is_rtl() { constants.scrollbar_gutter.x } else { 0.0 },
right: if constants.layout_direction.is_rtl() { 0.0 } else { constants.scrollbar_gutter.x },
top: 0.0,
bottom: constants.scrollbar_gutter.y,
};
let absolute_position_area = constants.container_size - absolute_position_inset.sum_axes();
let absolute_position_offset = Point { x: absolute_position_inset.left, y: absolute_position_inset.top };
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::ContentSize,
Line::FALSE,
);
}
}
let first_line = if constants.is_wrap_reverse { flex_lines.last() } else { flex_lines.first() };
let first_vertical_baseline = first_line.and_then(|line| {
if constants.is_column {
let item = if constants.dir.is_reverse() { line.items.last() } else { line.items.first() };
item.map(|child| child.baseline)
} else {
line.items
.iter()
.find(|item| item.participates_in_baseline_alignment(constants.dir))
.or_else(|| line.items.iter().next())
.map(|child| child.baseline)
}
});
let mut output = LayoutOutput::from_sizes_and_baselines(
constants.container_size,
inflow_overflow_rect,
Baselines::from_first(first_vertical_baseline),
);
output.oof_candidates = candidates;
output.oof_positioning_area =
Some(OofPositioningArea { size: absolute_position_area, offset: absolute_position_offset });
output
}
#[inline(always)]
fn resolve_normal_alignment(alignment: AlignItems) -> AlignItems {
match alignment.keyword {
AlignItemsKeyword::Normal => AlignItems::STRETCH,
_ => alignment,
}
}
#[inline]
fn compute_constants(
tree: &impl LayoutFlexboxContainer,
style: impl FlexboxContainerStyle,
known_dimensions: Size<Option<f32>>,
known_dimensions_are_definite: Size<bool>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
) -> AlgoConstants {
let dir = style.flex_direction();
let is_row = dir.is_row();
let is_column = dir.is_column();
let flex_wrap = style.flex_wrap();
let is_wrap = flex_wrap.is_multi_line();
let is_wrap_reverse = flex_wrap.is_reverse();
#[cfg(feature = "flexbox_balance")]
let is_balance = flex_wrap.is_balance();
#[cfg(feature = "flexbox_balance")]
let line_count = if is_wrap { Some(style.flex_line_count().max(1)) } else { None };
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 align_items = resolve_normal_alignment(style.align_items());
let align_content = match style.align_content() {
AlignContent { keyword: AlignContentKeyword::Normal, .. } => AlignContent::STRETCH,
align_content => align_content,
};
let justify_content = match style.justify_content() {
JustifyContent { keyword: AlignContentKeyword::Normal, .. } => JustifyContent::FLEX_START,
justify_content => justify_content,
};
let layout_direction = style.direction();
let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width(),
_ => 0.0,
});
#[cfg(feature = "content_size")]
let is_scroll_container = {
let overflow = style.overflow();
overflow.x.is_scroll_container() || overflow.y.is_scroll_container()
};
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 known_main_size_is_definite = known_dimensions_are_definite.main(dir);
let has_definite_main_size = known_main_size_is_definite && known_dimensions.main(dir).is_some();
let has_definite_cross_size = known_dimensions_are_definite.cross(dir) && known_dimensions.cross(dir).is_some();
let cross_axis_available_space_is_definite =
has_definite_cross_size || matches!(available_space.cross(dir), AvailableSpace::Definite(_));
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,
#[cfg(feature = "flexbox_balance")]
is_balance,
#[cfg(feature = "flexbox_balance")]
line_count,
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),
border,
gap,
content_box_inset,
scrollbar_gutter,
#[cfg(feature = "content_size")]
is_scroll_container,
align_items,
align_content,
justify_content,
node_outer_size,
node_inner_size,
known_main_size_is_definite,
has_definite_main_size,
has_definite_cross_size,
cross_axis_available_space_is_definite,
container_size,
inner_container_size,
}
}
fn resolve_relative_inset(inset: Rect<Option<f32>>, constants: &AlgoConstants) -> Size<f32> {
let direction = constants.dir;
let is_rtl_row = direction.is_row() && constants.layout_direction.is_rtl();
let is_rtl_column = direction.is_column() && constants.layout_direction.is_rtl();
let main = if is_rtl_row {
inset.main_end(direction).or(inset.main_start(direction).map(|pos| -pos)).unwrap_or(0.0)
} else {
inset.main_start(direction).or(inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0)
};
let cross = if is_rtl_column {
inset.cross_end(direction).map(|pos| -pos).or(inset.cross_start(direction)).unwrap_or(0.0)
} else {
inset.cross_start(direction).or(inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0)
};
let mut out = Size::ZERO;
out.set_main(direction, main);
out.set_cross(direction, cross);
out
}
#[inline]
fn generate_anonymous_flex_items(
tree: &impl LayoutFlexboxContainer,
node: NodeId,
constants: &AlgoConstants,
) -> Vec<FlexItem> {
let percent_resolution_size = if constants.known_main_size_is_definite {
constants.node_inner_size
} else {
constants.node_inner_size.with_main(constants.dir, None)
};
tree.child_ids(node)
.enumerate()
.map(|(index, child)| (index, child, tree.get_flexbox_child_style(child)))
.filter(|(_, _, style)| !style.position().is_out_of_flow())
.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(percent_resolution_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
size_style: child_style.size(),
min_size: child_style
.min_size()
.maybe_resolve(percent_resolution_size, |val, basis| tree.calc(val, basis))
.maybe_add(box_sizing_adjustment),
max_size: child_style
.max_size()
.maybe_resolve(percent_resolution_size, |val, basis| tree.calc(val, basis))
.maybe_add(box_sizing_adjustment),
aspect_ratio,
relative_inset: if child_style.position() == Position::Relative {
let inset = child_style.inset().zip_size(constants.node_inner_size, |p, s| {
p.maybe_resolve(s, |val, basis| tree.calc(val, basis))
});
resolve_relative_inset(inset, constants)
} else {
Size::ZERO
},
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: resolve_normal_alignment(child_style.align_self().unwrap_or(constants.align_items))
.resolve_self_relative(child_style.direction(), constants.layout_direction, constants.is_column),
overflow: child_style.overflow(),
contain: child_style.contain(),
scrollbar_width: child_style.scrollbar_width(),
flex_grow: child_style.flex_grow(),
flex_shrink: child_style.flex_shrink(),
flex_basis_is_definite: false,
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,
oof_candidates: OofCandidates::NONE,
}
})
.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()).max(0.0))
}
None => outer_available_space.width.maybe_sub(constants.content_box_inset.horizontal_axis_sum()).maybe_max(0.0),
};
let height = match known_dimensions.height {
Some(node_height) => {
AvailableSpace::Definite((node_height - constants.content_box_inset.vertical_axis_sum()).max(0.0))
}
None => outer_available_space.height.maybe_sub(constants.content_box_inset.vertical_axis_sum()).maybe_max(0.0),
};
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 transferred_min_size = child.min_size.maybe_apply_aspect_ratio(child.aspect_ratio);
let transferred_max_size = child.max_size.maybe_apply_aspect_ratio(child.aspect_ratio);
let child_min_cross = transferred_min_size.cross(dir);
let child_max_cross = transferred_max_size.cross(dir);
let cross_axis_available_space: AvailableSpace = match available_space.cross(dir) {
AvailableSpace::Definite(val) => AvailableSpace::Definite(
(constants.divided_cross_space(cross_axis_parent_size.unwrap_or(val))
- child.margin.cross_axis_sum(dir))
.max(0.0)
.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 mut child_cross_size_is_definite = child.size.cross(dir).is_some();
let child_known_dimensions = {
let mut ckd = child.size.with_main(dir, None);
ckd.set_cross(
dir,
ckd.cross(dir).maybe_clamp(transferred_min_size.cross(dir), transferred_max_size.cross(dir)),
);
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());
child_cross_size_is_definite =
!constants.is_wrap && constants.has_definite_cross_size && cross_axis_parent_size.is_some();
}
ckd
};
let container_width = constants.node_inner_size.width;
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 percent_resolution_main_size =
if constants.known_main_size_is_definite { constants.node_inner_size.main(dir) } else { None };
let flex_basis_style = child_style.flex_basis();
let flex_basis = flex_basis_style
.maybe_resolve(percent_resolution_main_size, |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);
let main_stretch_size =
percent_resolution_main_size.maybe_sub(child.margin.main_axis_sum(dir)).maybe_max(0.0);
let keyword_main_available_space = if flex_basis_style.is_content() {
None
} else if flex_basis_style.is_sizing_keyword() {
match resolve_sizing_keyword(
flex_basis_style,
main_stretch_size,
percent_resolution_main_size,
|val, basis| tree.calc(val, basis),
) {
Some(SizingKeywordResolution::Exact(size)) => {
child.flex_basis_is_definite = true;
break 'flex_basis size;
}
Some(SizingKeywordResolution::Measure(available)) => Some(available),
None => None,
}
} else {
if let Some(flex_basis) = flex_basis.or(main_size.filter(|_| flex_basis_style.is_auto())) {
child.flex_basis_is_definite = true;
break 'flex_basis flex_basis;
};
if flex_basis_style.is_auto() {
match resolve_sizing_keyword(
child.size_style.main(dir),
main_stretch_size,
percent_resolution_main_size,
|val, basis| tree.calc(val, basis),
) {
Some(SizingKeywordResolution::Exact(size)) => {
child.flex_basis_is_definite = true;
break 'flex_basis size;
}
Some(SizingKeywordResolution::Measure(available)) => Some(available),
None => None,
}
} else {
None
}
};
if child_cross_size_is_definite {
if let (Some(ratio), Some(cross)) = (child.aspect_ratio, child_known_dimensions.cross(dir)) {
child.flex_basis_is_definite = true;
break 'flex_basis if dir.is_row() { cross * ratio } else { cross / ratio };
}
}
let child_available_space = Size::MAX_CONTENT
.with_main(
dir,
keyword_main_available_space.unwrap_or(
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_else(|| {
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(transferred_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(transferred_min_size.main(constants.dir))
.maybe_max(padding_border_axes_sums.main(constants.dir));
let hypothetical_inner_size =
child.flex_basis.maybe_clamp(Some(hypothetical_inner_min_main), transferred_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 || !constants.known_main_size_is_definite {
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({
let available = available_space.main(constants.dir).into_option().unwrap_or(max_size);
let available = if constants.has_definite_main_size { available } else { available.min(max_size) };
available.maybe_max(constants.min_size.main(constants.dir))
}),
None if !constants.dir.is_row()
&& !constants.has_definite_main_size
&& available_space.main(constants.dir).is_definite() =>
{
AvailableSpace::MaxContent
}
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
}
}
}
}
#[cfg(feature = "flexbox_balance")]
fn collect_balanced_flex_lines<'a>(
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
flex_items: &'a mut [FlexItem],
) -> Vec<FlexLine<'a>> {
if flex_items.is_empty() {
return new_vec_with_capacity(0);
}
let main_axis_available_space = if constants.known_main_size_is_definite {
match constants.max_size.main(constants.dir) {
Some(max_size) => AvailableSpace::Definite({
let available = available_space.main(constants.dir).into_option().unwrap_or(max_size);
let available = if constants.has_definite_main_size { available } else { available.min(max_size) };
available.maybe_max(constants.min_size.main(constants.dir))
}),
None if !constants.dir.is_row()
&& !constants.has_definite_main_size
&& available_space.main(constants.dir).is_definite() =>
{
AvailableSpace::MaxContent
}
None => available_space.main(constants.dir),
}
} else {
AvailableSpace::MaxContent
};
if main_axis_available_space == 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;
}
return lines;
}
let line_break_size = main_axis_available_space.into_option().unwrap_or(f32::INFINITY);
let min_line_count = constants.line_count.unwrap_or(1) as usize;
let item_counts = balance::balanced_line_item_counts(
flex_items.iter().map(|item| item.hypothetical_outer_size.main(constants.dir)),
line_break_size,
constants.gap.main(constants.dir),
min_line_count,
);
let mut lines = new_vec_with_capacity(item_counts.len());
let mut items = &mut flex_items[..];
for count in item_counts {
let (line_items, rest) = items.split_at_mut(count);
lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
items = rest;
}
debug_assert!(items.is_empty());
lines
}
#[inline]
fn item_known_dimension_definiteness(constants: &AlgoConstants, item: &FlexItem) -> Size<bool> {
let dir = constants.dir;
let main_is_definite = constants.has_definite_main_size || item.flex_basis_is_definite;
let has_cross_auto_margins = item.margin_is_auto.cross_start(dir) || item.margin_is_auto.cross_end(dir);
let cross_size = item.size_style.cross(dir);
let is_stretched = !has_cross_auto_margins
&& (cross_size.is_stretch() || (item.align_self == AlignSelf::STRETCH && cross_size.is_auto()));
let cross_is_definite = is_stretched
|| item.size.cross(dir).is_some()
|| (!dir.is_row() && constants.cross_axis_available_space_is_definite);
Size { width: true, height: true }.with_main(dir, main_is_definite).with_cross(dir, cross_is_definite)
}
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 main_axis_gap = constants.gap.main(constants.dir);
let item_main_length = |child: &FlexItem| {
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)
};
let longest_line_length: f32 = lines
.iter()
.map(|line| {
let line_main_axis_gap = sum_axis_gaps(main_axis_gap, line.items.len());
let total_target_size = line.items.iter().map(item_main_length).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;
#[cfg(feature = "flexbox_balance")]
if constants.is_balance {
let min_line_count = constants.line_count.unwrap_or(1);
let item_count = lines.iter().map(|line| line.items.len()).sum::<usize>();
if item_count == 0 {
return size;
}
let mut item_lengths: Vec<f32> = new_vec_with_capacity(item_count);
for line in lines.iter() {
for child in line.items.iter() {
item_lengths.push(item_main_length(child));
}
}
let item_counts = balance::balanced_line_item_counts(
item_lengths.iter().copied(),
f32::INFINITY,
main_axis_gap,
min_line_count.max(1) as usize,
);
let mut widest_line_length: f32 = 0.0;
let mut index = 0;
for count in item_counts {
let line_length: f32 = item_lengths[index..index + count].iter().sum::<f32>()
+ sum_axis_gaps(main_axis_gap, count);
widest_line_length = widest_line_length.max(line_length);
index += count;
}
let max_content_size = widest_line_length + main_content_box_inset;
return f32_max(size, f32_min(max_content_size, main_axis_available_space));
}
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 = if constants.is_row {
Some(item.flex_basis).maybe_max(style_preferred)
} else {
Some(item.flex_basis)
};
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)
}
(_, Some(pref), _) => {
let item_pb_main = item.padding.main_axis_sum(constants.dir)
+ item.border.main_axis_sum(constants.dir);
let inner_main_size = pref.max(item_pb_main);
if constants.is_row {
(inner_main_size + item.margin.main_axis_sum(constants.dir))
.maybe_clamp(style_min, style_max)
} else {
(inner_main_size.max(item.flex_basis) + item.margin.main_axis_sum(constants.dir))
.maybe_clamp(style_min, style_max)
}
}
_ => {
let cross_axis_parent_size = constants.node_inner_size.cross(dir);
let child_min_cross = item.min_size.cross(dir);
let child_max_cross = item.max_size.cross(dir);
let cross_axis_available_space: AvailableSpace = available_space
.cross(dir)
.map_definite_value(|val| {
(constants.divided_cross_space(cross_axis_parent_size.unwrap_or(val))
- item.margin.cross_axis_sum(dir))
.max(0.0)
})
.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);
let transferred_min_size =
item.min_size.maybe_apply_aspect_ratio(item.aspect_ratio);
let transferred_max_size =
item.max_size.maybe_apply_aspect_ratio(item.aspect_ratio);
ckd.set_cross(
dir,
ckd.cross(dir).maybe_clamp(
transferred_min_size.cross(dir),
transferred_max_size.cross(dir),
),
);
if item.align_self == AlignSelf::STRETCH && ckd.cross(dir).is_none() {
ckd.set_cross(dir, cross_axis_available_space.into_option());
}
ckd
};
debug_log!("COMPUTE CHILD BASE SIZE (for intrinsic main size):");
let measured_main_size = tree.measure_child_size(
item.node,
child_known_dimensions,
constants.node_inner_size,
child_available_space,
SizingMode::ContentSize,
dir.main_axis(),
Line::FALSE,
);
let transferred_main_size = item
.aspect_ratio
.zip(child_known_dimensions.cross(dir))
.map(|(ratio, cross)| if constants.is_row { cross * ratio } else { cross / ratio });
let inner_main_size = measured_main_size.maybe_max(transferred_main_size);
if constants.is_row {
(inner_main_size + item.margin.main_axis_sum(constants.dir))
.maybe_clamp(style_min, style_max)
} else {
(inner_main_size.max(item.flex_basis) + item.margin.main_axis_sum(constants.dir))
.maybe_clamp(style_min, style_max)
}
}
};
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;
if scaled_shrink_factor == 0.0 {
0.0
} else {
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_available_main =
(constants.container_size.main(constants.dir) - child.margin.main_axis_sum(constants.dir)).max(0.0).into();
let transferred_min_cross = child.min_size.maybe_apply_aspect_ratio(child.aspect_ratio).cross(constants.dir);
let transferred_max_cross = child.max_size.maybe_apply_aspect_ratio(child.aspect_ratio).cross(constants.dir);
let child_cross = child
.size
.cross(constants.dir)
.maybe_clamp(transferred_min_cross, transferred_max_cross)
.maybe_max(padding_border_sum);
let child_available_cross = available_space
.cross(constants.dir)
.map_definite_value(|val| {
(constants.divided_cross_space(val) - child.margin.cross_axis_sum(constants.dir)).max(0.0)
})
.maybe_clamp(transferred_min_cross, transferred_max_cross)
.maybe_max(padding_border_sum);
let cross_stretch_size = constants
.node_inner_size
.cross(constants.dir)
.map(|val| constants.divided_cross_space(val))
.maybe_sub(child.margin.cross_axis_sum(constants.dir))
.maybe_max(0.0);
let child_available_cross = match resolve_sizing_keyword(
child.size_style.cross(constants.dir),
cross_stretch_size,
constants.node_inner_size.cross(constants.dir),
|val, basis| tree.calc(val, basis),
) {
Some(SizingKeywordResolution::Measure(available)) => available,
_ => child_available_cross,
};
let child_inner_cross = child_cross.unwrap_or_else(|| {
tree.compute_child_layout(
child.node,
LayoutInput {
run_mode: RunMode::ComputeSize,
sizing_mode: SizingMode::ContentSize,
axis: constants.dir.cross_axis().into(),
known_dimensions: 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() },
},
known_dimensions_are_definite: item_known_dimension_definiteness(constants, child),
parent_size: constants.node_inner_size,
available_space: Size {
width: if constants.is_row { child_available_main } else { child_available_cross },
height: if constants.is_row { child_available_cross } else { child_available_main },
},
vertical_margins_are_collapsible: Line::FALSE,
},
)
.size
.get_abs(constants.dir.cross_axis())
.maybe_clamp(transferred_min_cross, transferred_max_cross)
.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.participates_in_baseline_alignment(constants.dir)).count();
if line_baseline_child_count <= 1 {
continue;
}
for child in line.items.iter_mut() {
if !child.participates_in_baseline_alignment(constants.dir) {
continue;
}
let measured_size_and_baselines = tree.compute_child_layout(
child.node,
LayoutInput {
run_mode: RunMode::PerformLayout,
sizing_mode: SizingMode::ContentSize,
axis: RequestedAxis::Both,
known_dimensions: 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()
},
},
known_dimensions_are_definite: item_known_dimension_definiteness(constants, child),
parent_size: constants.node_inner_size,
available_space: 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()
},
}
.maybe_sub(child.margin.sum_axes())
.maybe_max(Size::ZERO),
vertical_margins_are_collapsible: Line::FALSE,
},
);
let baseline = measured_size_and_baselines.baselines.first;
let height = measured_size_and_baselines.size.height;
let baseline = if child.overflow.y.is_scroll_container() {
baseline.unwrap_or(height).min(height).max(0.0)
} else {
baseline.unwrap_or(height)
};
child.baseline = baseline + 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.participates_in_baseline_alignment(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);
let cross_is_stretch = child.size_style.cross(constants.dir).is_stretch();
child.target_size.set_cross(
constants.dir,
if !child.margin_is_auto.cross_start(constants.dir)
&& !child.margin_is_auto.cross_end(constants.dir)
&& (cross_is_stretch
|| (child.align_self == AlignSelf::STRETCH
&& child_style.size().cross(constants.dir).is_auto()))
{
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 };
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)).max(0.0).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 mut 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;
}
}
}
free_space = 0.0;
}
let num_items = line.items.len();
let layout_reverse = constants.dir.is_reverse();
let gap = constants.gap.main(constants.dir);
let justify_content_mode = apply_alignment_fallback(free_space, num_items, constants.justify_content);
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));
let max_baseline_to_bottom_distance: f32 = line
.items
.iter_mut()
.filter(|child| child.participates_in_baseline_alignment(constants.dir))
.map(|child| child.outer_target_size.cross(constants.dir) - 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,
max_baseline_to_bottom_distance,
constants,
);
}
}
}
}
#[inline]
fn align_flex_items_along_cross_axis(
child: &FlexItem,
free_space: f32,
max_baseline: f32,
max_baseline_to_bottom_distance: f32,
constants: &AlgoConstants,
) -> f32 {
let cross_axis_should_reverse = constants.is_column && matches!(constants.layout_direction, Direction::Rtl);
let align_keyword = if child.align_self.is_safe() && free_space < 0.0 {
AlignItemsKeyword::Start
} else {
child.align_self.keyword
};
match align_keyword {
AlignItemsKeyword::Start => {
if cross_axis_should_reverse {
free_space
} else {
0.0
}
}
AlignItemsKeyword::FlexStart => {
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
free_space
} else {
0.0
}
}
AlignItemsKeyword::End => {
if cross_axis_should_reverse {
0.0
} else {
free_space
}
}
AlignItemsKeyword::FlexEnd => {
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
0.0
} else {
free_space
}
}
AlignItemsKeyword::Center => free_space / 2.0,
AlignItemsKeyword::Baseline => {
if constants.is_row {
if constants.is_wrap_reverse {
let line_cross_size = free_space + child.outer_target_size.cross(constants.dir);
line_cross_size - max_baseline_to_bottom_distance - child.baseline
} else {
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
}
}
}
AlignItemsKeyword::Stretch => {
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
free_space
} else {
0.0
}
}
AlignItemsKeyword::Normal | AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd => unreachable!(),
}
}
#[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 align_content_mode = apply_alignment_fallback(free_space, num_lines, constants.align_content);
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_overflow_rect: &mut Rect<f32>,
#[cfg(feature = "content_size")] border: Rect<f32>,
constants: &AlgoConstants,
) {
let container_size = constants.container_size;
let node_inner_size = constants.node_inner_size;
let direction = constants.dir;
let layout_direction = constants.layout_direction;
let item_known_dimension_definiteness = item_known_dimension_definiteness(constants, item);
let mut layout_output = tree.compute_child_layout(
item.node,
LayoutInput {
run_mode: RunMode::PerformLayout,
sizing_mode: SizingMode::ContentSize,
axis: RequestedAxis::Both,
known_dimensions: item.target_size.map(|s| s.into()),
known_dimensions_are_definite: item_known_dimension_definiteness,
parent_size: node_inner_size,
available_space: (container_size - item.margin.sum_axes()).map(|s| s.max(0.0).into()),
vertical_margins_are_collapsible: Line::FALSE,
},
);
let LayoutOutput {
size,
#[cfg(feature = "content_size")]
scrollable_overflow_rect,
..
} = 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 = item.relative_inset.main(direction);
let cross_relative_inset = item.relative_inset.cross(direction);
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 = {
let baseline = layout_output.baselines.first.unwrap_or(size.height);
if item.overflow.y.is_scroll_container() {
baseline.min(size.height).max(0.0)
} else {
baseline
}
};
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.baselines.first.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")]
scrollable_overflow_rect,
scrollbar_size,
location,
padding: item.padding,
border: item.border,
margin: item.margin,
},
);
item.oof_candidates = layout_output.oof_candidates.take();
if !item.oof_candidates.is_empty() {
item.oof_candidates.translate(location);
}
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) - border.right, y: location.y - border.top }
} else {
Point { x: location.x - border.left, y: location.y - border.top }
};
*total_overflow_rect = total_overflow_rect.union(compute_scrollable_overflow_contribution(
contribution_location,
size,
scrollable_overflow_rect,
item.overflow,
item.contain,
constants.is_scroll_container,
));
}
}
#[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")] overflow_rect: &mut Rect<f32>,
#[cfg(feature = "content_size")] border: Rect<f32>,
constants: &AlgoConstants,
) {
let container_size = constants.container_size;
let padding_border = constants.content_box_inset;
let direction = constants.dir;
let layout_direction = constants.layout_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")]
overflow_rect,
#[cfg(feature = "content_size")]
border,
constants,
);
}
} 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")]
overflow_rect,
#[cfg(feature = "content_size")]
border,
constants,
);
}
}
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,
) -> Rect<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 overflow_rect = Rect::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 overflow_rect,
#[cfg(feature = "content_size")]
constants.border,
constants,
);
}
} else {
for line in flex_lines.iter_mut() {
calculate_layout_line(
tree,
line,
&mut total_offset_cross,
#[cfg(feature = "content_size")]
&mut overflow_rect,
#[cfg(feature = "content_size")]
constants.border,
constants,
);
}
}
#[cfg(feature = "content_size")]
if constants.is_scroll_container {
overflow_rect.right += 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
};
overflow_rect.bottom +=
constants.content_box_inset.bottom - constants.border.bottom - constants.scrollbar_gutter.y;
}
overflow_rect
}
#[inline]
fn collect_oof_candidates(
tree: &mut impl LayoutFlexboxContainer,
node: NodeId,
constants: &AlgoConstants,
flex_lines: &mut [FlexLine],
candidates: &mut OofCandidates,
) {
let mut items = flex_lines
.iter_mut()
.flat_map(|line| line.items.iter_mut())
.filter(|item| !item.oof_candidates.is_empty())
.peekable();
let dir = constants.dir;
let container_size = constants.container_size;
let content_box_inset = constants.content_box_inset;
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 = dir.is_reverse() ^ main_is_rtl;
let cross_axis_flex_start_reversed = constants.is_wrap_reverse ^ cross_is_rtl;
let main_area = Line {
start: content_box_inset.main_start(dir),
end: container_size.main(dir) - content_box_inset.main_end(dir),
};
let cross_area = Line {
start: content_box_inset.cross_start(dir),
end: container_size.cross(dir) - content_box_inset.cross_end(dir),
};
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);
let position = child_style.position();
if child_style.box_generation_mode() == BoxGenerationMode::None {
continue;
}
if !position.is_out_of_flow() {
drop(child_style);
if let Some(item) = items.next_if(|item| item.order == order as u32) {
candidates.append(&mut item.oof_candidates);
}
continue;
}
let align_self = resolve_normal_alignment(child_style.align_self().unwrap_or(constants.align_items))
.resolve_self_relative(child_style.direction(), constants.layout_direction, constants.is_column);
drop(child_style);
let main_keyword = constants.justify_content.keyword();
let main_start_position = match main_keyword {
AlignContentKeyword::Start => !main_is_rtl,
AlignContentKeyword::End => main_is_rtl,
_ => true,
};
let main_edge = match (main_keyword, main_axis_flex_start_reversed) {
(AlignContentKeyword::SpaceBetween, false)
| (AlignContentKeyword::Normal | AlignContentKeyword::Stretch, false)
| (AlignContentKeyword::FlexStart, false)
| (AlignContentKeyword::FlexEnd, true) => AxisStaticEdge::Start,
(AlignContentKeyword::Start | AlignContentKeyword::End, _) => {
if main_start_position {
AxisStaticEdge::Start
} else {
AxisStaticEdge::End
}
}
(AlignContentKeyword::FlexEnd, false)
| (AlignContentKeyword::FlexStart, true)
| (AlignContentKeyword::Normal | AlignContentKeyword::Stretch, true)
| (AlignContentKeyword::SpaceBetween, true) => AxisStaticEdge::End,
(AlignContentKeyword::SpaceEvenly, _)
| (AlignContentKeyword::SpaceAround, _)
| (AlignContentKeyword::Center, _) => AxisStaticEdge::Center,
};
let cross_edge_for = |keyword: AlignItemsKeyword| {
let start_position = match keyword {
AlignItemsKeyword::Start | AlignItemsKeyword::Baseline => !cross_is_rtl,
AlignItemsKeyword::End => cross_is_rtl,
_ => true,
};
match (keyword, cross_axis_flex_start_reversed) {
(AlignItemsKeyword::Start | AlignItemsKeyword::End | AlignItemsKeyword::Baseline, _) => {
if start_position {
AxisStaticEdge::Start
} else {
AxisStaticEdge::End
}
}
(AlignItemsKeyword::Stretch | AlignItemsKeyword::FlexStart, false)
| (AlignItemsKeyword::FlexEnd, true) => AxisStaticEdge::Start,
(AlignItemsKeyword::Stretch | AlignItemsKeyword::FlexStart, true)
| (AlignItemsKeyword::FlexEnd, false) => AxisStaticEdge::End,
(AlignItemsKeyword::Center, _) => AxisStaticEdge::Center,
(AlignItemsKeyword::Normal | AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd, _) => {
unreachable!()
}
}
};
let cross = AxisStaticPosition {
area: cross_area,
align: AxisStaticAlign {
keyword: cross_edge_for(align_self.keyword),
safety: align_self.safety,
fallback: cross_edge_for(resolve_self_alignment_safety(align_self, true)),
},
};
let main = AxisStaticPosition { area: main_area, align: AxisStaticAlign::from_keyword(main_edge) };
let static_position = if constants.is_row { Point { x: main, y: cross } } else { Point { x: cross, y: main } };
candidates.push(OofCandidate { node: child, order: order as u32, position, static_position });
}
}
#[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
}
}
#[cfg(feature = "flexbox_balance")]
mod balance {
use crate::util::sys::{new_vec_with_capacity, Vec};
const INFEASIBLE: f64 = f64::INFINITY;
fn to_size(value: f32) -> f64 {
(value as f64).clamp(0.0, f32::MAX as f64)
}
struct LineSizes {
sums: Vec<(f64, bool)>,
gap_between_items: f64,
limit: f64,
}
impl LineSizes {
fn item_count(&self) -> usize {
self.sums.len()
}
fn is_zero_item(&self, index: usize) -> bool {
self.sums[index].1
}
fn line_size(&self, start: usize, end: usize) -> f64 {
let start_sum = if start == 0 { 0.0 } else { self.sums[start - 1].0 };
self.sums[end].0 - start_sum - self.gap_between_items
}
fn line_cost(&self, start: usize, end: usize) -> f64 {
let size = self.line_size(start, end);
if end > start && size > self.limit {
return INFEASIBLE;
}
size * size
}
fn suffix_greedy_line_counts(&self) -> Vec<u32> {
let item_count = self.item_count();
let mut counts: Vec<u32> = new_vec_with_capacity(item_count + 1);
counts.extend(core::iter::repeat(0).take(item_count + 1));
let mut line_end = item_count;
for start in (0..item_count).rev() {
while line_end > start + 1 && self.line_size(start, line_end - 1) > self.limit {
line_end -= 1;
}
counts[start] = 1 + counts[line_end];
}
counts
}
fn fit_ends(&self) -> Vec<u32> {
let item_count = self.item_count();
let mut fit_ends: Vec<u32> = new_vec_with_capacity(item_count);
let mut fit_end = 0;
for start in 0..item_count {
if fit_end < start {
fit_end = start;
}
while fit_end + 1 < item_count && self.line_size(start, fit_end + 1) <= self.limit {
fit_end += 1;
}
fit_ends.push(fit_end as u32);
}
fit_ends
}
}
struct Row<'a> {
sizes: &'a LineSizes,
fit_ends: &'a [u32],
prev: &'a [f64],
nonzero_ends: &'a [u32],
max_end: usize,
}
fn fill_row(
row: &Row,
cur: &mut [f64],
opts: &mut [u32],
start_lo: usize,
start_hi: usize,
col_lo: usize,
col_hi: usize,
) {
if start_lo > start_hi {
return;
}
let start = start_lo + (start_hi - start_lo) / 2;
let first_col = col_lo + row.nonzero_ends[col_lo..col_hi].partition_point(|&end| (end as usize) < start);
let mut min_cost = INFEASIBLE;
let mut min_col = None;
for col in first_col..col_hi {
let end = row.nonzero_ends[col] as usize;
let line_cost = row.sizes.line_cost(start, end);
if line_cost == INFEASIBLE {
break;
}
let cost = line_cost + row.prev[end + 1];
if cost <= min_cost {
min_cost = cost;
min_col = Some(col);
}
}
let mut min_end = min_col.map(|col| row.nonzero_ends[col] as usize);
let zero_end = (row.fit_ends[start] as usize).min(row.max_end);
if row.sizes.is_zero_item(zero_end + 1) {
let cost = row.sizes.line_cost(start, zero_end) + row.prev[zero_end + 1];
if cost < min_cost || (cost == min_cost && min_end.map_or(true, |end| end < zero_end)) {
min_cost = cost;
min_end = Some(zero_end);
}
}
debug_assert!(min_cost.is_finite());
cur[start] = min_cost;
opts[start] = min_end.unwrap_or(start) as u32;
if start > start_lo {
let col_hi = min_col.map_or(col_hi, |col| col + 1);
fill_row(row, cur, opts, start_lo, start - 1, col_lo, col_hi);
}
if start < start_hi {
let col_lo = min_col.unwrap_or(first_col);
fill_row(row, cur, opts, start + 1, start_hi, col_lo, col_hi);
}
}
pub(super) fn balanced_line_item_counts(
item_sizes: impl ExactSizeIterator<Item = f32>,
line_limit: f32,
gap_between_items: f32,
min_line_count: usize,
) -> Vec<usize> {
let item_count = item_sizes.len();
debug_assert!(item_count > 0);
let gap_between_items = to_size(gap_between_items);
let limit = line_limit as f64;
let mut sums: Vec<(f64, bool)> = new_vec_with_capacity(item_count);
let mut sum = 0.0;
for size in item_sizes {
let size = to_size(size);
sum += size + gap_between_items;
sums.push((sum, size == 0.0));
}
let sizes = LineSizes { sums, gap_between_items, limit };
let suffix_greedy = sizes.suffix_greedy_line_counts();
let line_count = (suffix_greedy[0] as usize).max(min_line_count.clamp(1, item_count));
let mut item_counts: Vec<usize> = new_vec_with_capacity(line_count);
if line_count == item_count {
item_counts.extend(core::iter::repeat(1).take(item_count));
return item_counts;
}
let fit_ends = sizes.fit_ends();
let mut nonzero_ends: Vec<u32> = new_vec_with_capacity(item_count - 1);
for end in 0..item_count - 1 {
if !sizes.is_zero_item(end + 1) {
nonzero_ends.push(end as u32);
}
}
let mut prev: Vec<f64> = new_vec_with_capacity(item_count);
for start in 0..item_count {
prev.push(sizes.line_cost(start, item_count - 1));
}
let mut cur: Vec<f64> = new_vec_with_capacity(item_count);
cur.extend(core::iter::repeat(INFEASIBLE).take(item_count));
let mut opts: Vec<u32> = new_vec_with_capacity((line_count - 1) * item_count);
opts.extend(core::iter::repeat(0).take((line_count - 1) * item_count));
for lines in 2..=line_count {
let max_end = item_count - lines;
let mut first_start = 0;
while first_start < max_end && suffix_greedy[first_start] as usize > lines {
cur[first_start] = INFEASIBLE;
first_start += 1;
}
let col_hi = nonzero_ends.partition_point(|&end| (end as usize) <= max_end);
let row = Row { sizes: &sizes, fit_ends: &fit_ends, prev: &prev, nonzero_ends: &nonzero_ends, max_end };
let opts_row = &mut opts[(lines - 2) * item_count..(lines - 1) * item_count];
fill_row(&row, &mut cur, opts_row, first_start, max_end, 0, col_hi);
core::mem::swap(&mut prev, &mut cur);
}
debug_assert!(prev[0].is_finite());
let mut start = 0;
for lines in (2..=line_count).rev() {
let end = opts[(lines - 2) * item_count + start] as usize;
item_counts.push(end - start + 1);
start = end + 1;
}
item_counts.push(item_count - start);
item_counts
}
#[cfg(test)]
mod tests {
use super::*;
fn greedy_line_count(sizes: &LineSizes) -> usize {
let item_count = sizes.item_count();
let mut line_count = 1;
let mut index = 0;
while index < item_count {
let mut next = index;
while next < item_count && sizes.line_size(index, next) <= sizes.limit {
next += 1;
}
if next == index {
next = index + 1;
}
index = next;
if index < item_count {
line_count += 1;
}
}
line_count
}
fn zero_boundary_valid(sizes: &LineSizes, min_errors: &[f64], lines: usize, start: usize, end: usize) -> bool {
let item_count = sizes.item_count();
if !sizes.is_zero_item(end + 1) {
return true;
}
for glued_end in end + 1..=item_count - lines {
if sizes.line_size(start, glued_end) > sizes.limit {
break;
}
if min_errors[(lines - 2) * item_count + glued_end + 1].is_finite() {
return false;
}
}
true
}
fn naive_line_item_counts(
item_sizes: &[f32],
line_limit: f32,
gap_between_items: f32,
min_line_count: usize,
) -> Vec<usize> {
let item_count = item_sizes.len();
let gap_between_items = to_size(gap_between_items);
let limit = line_limit as f64;
let mut sums: Vec<(f64, bool)> = new_vec_with_capacity(item_count);
let mut sum = 0.0;
for size in item_sizes {
let size = to_size(*size);
sum += size + gap_between_items;
sums.push((sum, size == 0.0));
}
let sizes = LineSizes { sums, gap_between_items, limit };
let line_count = greedy_line_count(&sizes).max(min_line_count.clamp(1, item_count));
let mut min_errors: Vec<f64> = new_vec_with_capacity(line_count * item_count);
for start in 0..item_count {
min_errors.push(sizes.line_cost(start, item_count - 1));
}
for lines in 2..=line_count {
let row = (lines - 1) * item_count;
for start in 0..item_count {
let mut min_error = INFEASIBLE;
let mut end = start;
while end + lines <= item_count {
let line_cost = sizes.line_cost(start, end);
if line_cost == INFEASIBLE {
break;
}
if zero_boundary_valid(&sizes, &min_errors, lines, start, end) {
let error = line_cost + min_errors[row - item_count + end + 1];
if error < min_error {
min_error = error;
}
}
end += 1;
}
min_errors.push(min_error);
}
}
assert!(min_errors[(line_count - 1) * item_count].is_finite());
let mut item_counts: Vec<usize> = new_vec_with_capacity(line_count);
let mut start = 0;
for lines_after in (0..line_count).rev() {
let target = min_errors[lines_after * item_count + start];
let mut end = item_count - 1 - lines_after;
loop {
if sizes.line_cost(start, end) != INFEASIBLE
&& (lines_after == 0 || zero_boundary_valid(&sizes, &min_errors, lines_after + 1, start, end))
{
let remaining_error =
if lines_after == 0 { 0.0 } else { min_errors[(lines_after - 1) * item_count + end + 1] };
if sizes.line_cost(start, end) + remaining_error == target {
break;
}
}
assert!(end > start);
end -= 1;
}
item_counts.push(end - start + 1);
start = end + 1;
}
assert_eq!(start, item_count);
item_counts
}
#[test]
fn zero_sized_item_rule() {
let counts = balanced_line_item_counts([70.0, 0.0, 20.0].iter().copied(), 100.0, 10.0, 1);
assert_eq!(counts, [2, 1]);
let counts = balanced_line_item_counts([150.0, 0.0, 30.0].iter().copied(), 100.0, 0.0, 1);
assert_eq!(counts, [1, 2]);
let counts = balanced_line_item_counts([70.0, 0.0, 0.0].iter().copied(), 100.0, 0.0, 2);
assert_eq!(counts, [2, 1]);
}
#[test]
fn matches_naive_dp() {
let mut state: u64 = 0x243F6A8885A308D3;
let mut rand = move |bound: u32| -> u32 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
((state >> 32) as u32) % bound
};
for case in 0..5000 {
let item_count = if case % 20 == 0 { (rand(60) + 1) as usize } else { (rand(15) + 1) as usize };
let mut item_sizes: Vec<f32> = new_vec_with_capacity(item_count);
for _ in 0..item_count {
let size = match rand(8) {
0 | 1 => 0.0,
2 => -10.0,
_ => rand(8) as f32 * 25.0,
};
item_sizes.push(size);
}
let line_limit = match rand(5) {
0 => f32::INFINITY,
_ => rand(10) as f32 * 30.0,
};
let gap_between_items = rand(4) as f32 * 5.0;
let min_line_count = rand(item_count as u32 + 2) as usize;
let expected = naive_line_item_counts(&item_sizes, line_limit, gap_between_items, min_line_count);
let actual = balanced_line_item_counts(
item_sizes.iter().copied(),
line_limit,
gap_between_items,
min_line_count,
);
assert_eq!(
actual, expected,
"case {case}: item_sizes={item_sizes:?} line_limit={line_limit} \
gap_between_items={gap_between_items} min_line_count={min_line_count}"
);
}
}
}
}