use core::f32;
use crate::compute::common::alignment::compute_alignment_offset;
use crate::compute::compute_node_layout;
use crate::geometry::{Point, Rect, Size};
use crate::layout::{Layout, RunMode, SizingMode};
use crate::math::MaybeMath;
use crate::node::Node;
use crate::prelude::TaffyMaxContent;
use crate::resolve::{MaybeResolve, ResolveOrZero};
use crate::style::{
AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
LengthPercentageAuto, PositionType,
};
use crate::style::{FlexDirection, Style};
use crate::sys::Vec;
use crate::tree::LayoutTree;
#[cfg(feature = "debug")]
use crate::debug::NODE_LOGGER;
struct FlexItem {
node: Node,
size: Size<Option<f32>>,
min_size: Size<Option<f32>>,
max_size: Size<Option<f32>>,
align_self: AlignSelf,
position: Rect<Option<f32>>,
margin: Rect<f32>,
padding: Rect<f32>,
border: Rect<f32>,
flex_basis: f32,
inner_flex_basis: f32,
violation: f32,
frozen: bool,
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,
}
struct FlexLine<'a> {
items: &'a mut [FlexItem],
container_main_size_contribution: f32,
cross_size: f32,
offset_cross: f32,
}
struct AlgoConstants {
dir: FlexDirection,
is_row: bool,
is_column: bool,
is_wrap_reverse: bool,
margin: Rect<f32>,
border: Rect<f32>,
padding_border: Rect<f32>,
gap: Size<f32>,
align_items: AlignItems,
node_inner_size: Size<Option<f32>>,
container_size: Size<f32>,
inner_container_size: Size<f32>,
}
pub fn compute(
tree: &mut impl LayoutTree,
node: Node,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
) -> Size<f32> {
let style = tree.style(node);
let has_min_max_sizes = style.min_size.width.is_defined()
|| style.min_size.height.is_defined()
|| style.max_size.width.is_defined()
|| style.max_size.height.is_defined();
let min_size = style.min_size.maybe_resolve(known_dimensions);
let max_size = style.max_size.maybe_resolve(known_dimensions);
let clamped_style_size = style.size.maybe_resolve(known_dimensions).maybe_clamp(min_size, max_size);
if has_min_max_sizes {
#[cfg(feature = "debug")]
NODE_LOGGER.log("FLEX: two-pass");
let first_pass = compute_preliminary(
tree,
node,
known_dimensions.zip_map(clamped_style_size, |known, style| known.or(style)),
available_space,
RunMode::ComputeSize,
);
let clamped_first_pass_size = first_pass.maybe_clamp(min_size, max_size);
compute_preliminary(
tree,
node,
known_dimensions.zip_map(clamped_first_pass_size, |known, first_pass| known.or_else(|| first_pass.into())),
available_space,
run_mode,
)
} else {
#[cfg(feature = "debug")]
NODE_LOGGER.log("FLEX: single-pass");
compute_preliminary(tree, node, known_dimensions.or(clamped_style_size), available_space, run_mode)
}
}
fn compute_preliminary(
tree: &mut impl LayoutTree,
node: Node,
known_dimensions: Size<Option<f32>>,
parent_size: Size<AvailableSpace>,
run_mode: RunMode,
) -> Size<f32> {
let mut constants = compute_constants(tree.style(node), known_dimensions, parent_size);
#[cfg(feature = "debug")]
NODE_LOGGER.log("generate_anonymous_flex_items");
let mut flex_items = generate_anonymous_flex_items(tree, node, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("determine_available_space");
let available_space = determine_available_space(known_dimensions, parent_size, &constants);
let has_baseline_child = flex_items.iter().any(|child| child.align_self == AlignSelf::Baseline);
#[cfg(feature = "debug")]
NODE_LOGGER.log("determine_flex_base_size");
determine_flex_base_size(tree, known_dimensions, &constants, available_space, &mut flex_items);
#[cfg(feature = "debug")]
NODE_LOGGER.log("collect_flex_lines");
let mut flex_lines = collect_flex_lines(tree, node, &constants, available_space, &mut flex_items);
let original_gap = constants.gap;
if constants.node_inner_size.main(constants.dir).is_none() {
let longest_line_length = flex_lines.iter().fold(f32::MIN, |acc, line| {
let length: f32 = line.items.iter().map(|item| item.hypothetical_outer_size.main(constants.dir)).sum();
acc.max(length)
});
let style = tree.style(node);
let new_gap = style.gap.main(constants.dir).maybe_resolve(longest_line_length).unwrap_or(0.0);
constants.gap.set_main(constants.dir, new_gap);
}
#[cfg(feature = "debug")]
NODE_LOGGER.log("resolve_flexible_lengths");
for line in &mut flex_lines {
resolve_flexible_lengths(tree, line, &constants, original_gap, available_space);
}
constants.container_size.set_main(
constants.dir,
known_dimensions.main(constants.dir).unwrap_or({
let longest_line =
flex_lines.iter().fold(f32::MIN, |acc, line| acc.max(line.container_main_size_contribution));
let size = longest_line + constants.padding_border.main_axis_sum(constants.dir);
match available_space.main(constants.dir) {
AvailableSpace::Definite(val) if flex_lines.len() > 1 && size < val => val,
_ => size,
}
}),
);
constants.inner_container_size.set_main(
constants.dir,
constants.container_size.main(constants.dir) - constants.padding_border.main_axis_sum(constants.dir),
);
#[cfg(feature = "debug")]
NODE_LOGGER.log("determine_hypothetical_cross_size");
for line in &mut flex_lines {
determine_hypothetical_cross_size(tree, line, &constants, available_space);
}
if has_baseline_child {
#[cfg(feature = "debug")]
NODE_LOGGER.log("calculate_children_base_lines");
calculate_children_base_lines(tree, node, known_dimensions, available_space, &mut flex_lines, &constants);
}
#[cfg(feature = "debug")]
NODE_LOGGER.log("calculate_cross_size");
calculate_cross_size(tree, &mut flex_lines, known_dimensions, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("handle_align_content_stretch");
handle_align_content_stretch(tree, &mut flex_lines, node, known_dimensions, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("determine_used_cross_size");
determine_used_cross_size(tree, &mut flex_lines, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("distribute_remaining_free_space");
distribute_remaining_free_space(tree, &mut flex_lines, node, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("resolve_cross_axis_auto_margins");
resolve_cross_axis_auto_margins(tree, &mut flex_lines, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("determine_container_cross_size");
let total_line_cross_size = determine_container_cross_size(&mut flex_lines, known_dimensions, &mut constants);
if run_mode == RunMode::ComputeSize {
let container_size = constants.container_size;
return container_size;
}
#[cfg(feature = "debug")]
NODE_LOGGER.log("align_flex_lines_per_align_content");
align_flex_lines_per_align_content(tree, &mut flex_lines, node, &constants, total_line_cross_size);
#[cfg(feature = "debug")]
NODE_LOGGER.log("final_layout_pass");
final_layout_pass(tree, node, &mut flex_lines, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("perform_absolute_layout_on_absolute_children");
perform_absolute_layout_on_absolute_children(tree, node, &constants);
#[cfg(feature = "debug")]
NODE_LOGGER.log("hidden_layout");
let len = tree.child_count(node);
for order in 0..len {
let child = tree.child(node, order);
if tree.style(child).display == Display::None {
*tree.layout_mut(node) = Layout::with_order(order as u32);
compute_node_layout(
tree,
child,
Size::NONE,
Size::MAX_CONTENT,
RunMode::PeformLayout,
SizingMode::InherentSize,
);
}
}
constants.container_size
}
#[inline]
fn compute_constants(style: &Style, node_size: Size<Option<f32>>, parent_size: Size<AvailableSpace>) -> AlgoConstants {
let dir = style.flex_direction;
let is_row = dir.is_row();
let is_column = dir.is_column();
let is_wrap_reverse = style.flex_wrap == FlexWrap::WrapReverse;
let margin = style.margin.resolve_or_zero(parent_size.width.into_option());
let padding = style.padding.resolve_or_zero(parent_size.width.into_option());
let border = style.border.resolve_or_zero(parent_size.width.into_option());
let align_items = style.align_items.unwrap_or(crate::style::AlignItems::Stretch);
let padding_border = Rect {
left: padding.left + border.left,
right: padding.right + border.right,
top: padding.top + border.top,
bottom: padding.bottom + border.bottom,
};
let node_inner_size = Size {
width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
};
let gap = style.gap.resolve_or_zero(node_inner_size.or(Size::zero()));
let container_size = Size::zero();
let inner_container_size = Size::zero();
AlgoConstants {
dir,
is_row,
is_column,
is_wrap_reverse,
margin,
border,
gap,
padding_border,
align_items,
node_inner_size,
container_size,
inner_container_size,
}
}
#[inline]
fn generate_anonymous_flex_items(tree: &impl LayoutTree, node: Node, constants: &AlgoConstants) -> Vec<FlexItem> {
tree.children(node)
.map(|child| (child, tree.style(*child)))
.filter(|(_, style)| style.position_type != PositionType::Absolute)
.filter(|(_, style)| style.display != Display::None)
.map(|(child, child_style)| FlexItem {
node: *child,
size: child_style.size.maybe_resolve(constants.node_inner_size),
min_size: child_style.min_size.maybe_resolve(constants.node_inner_size),
max_size: child_style.max_size.maybe_resolve(constants.node_inner_size),
position: child_style.position.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
margin: child_style.margin.resolve_or_zero(constants.node_inner_size.width),
padding: child_style.padding.resolve_or_zero(constants.node_inner_size.width),
border: child_style.border.resolve_or_zero(constants.node_inner_size.width),
align_self: child_style.align_self.unwrap_or(constants.align_items),
flex_basis: 0.0,
inner_flex_basis: 0.0,
violation: 0.0,
frozen: false,
hypothetical_inner_size: Size::zero(),
hypothetical_outer_size: Size::zero(),
target_size: Size::zero(),
outer_target_size: Size::zero(),
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),
None => outer_available_space
.width
.maybe_sub(constants.margin.horizontal_axis_sum())
.maybe_sub(constants.padding_border.horizontal_axis_sum()),
};
let height = match known_dimensions.height {
Some(node_height) => AvailableSpace::Definite(node_height),
None => outer_available_space
.height
.maybe_sub(constants.margin.vertical_axis_sum())
.maybe_sub(constants.padding_border.vertical_axis_sum()),
};
Size { width, height }
}
#[inline]
fn determine_flex_base_size(
tree: &mut impl LayoutTree,
node_size: Size<Option<f32>>,
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
flex_items: &mut Vec<FlexItem>,
) {
for child in flex_items.iter_mut() {
let child_style = tree.style(child.node);
let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
if flex_basis.is_some() {
child.flex_basis = flex_basis.unwrap_or(0.0);
continue;
};
if let Some(ratio) = child_style.aspect_ratio {
if let Some(cross) = node_size.cross(constants.dir) {
if child_style.flex_basis == Dimension::Auto {
child.flex_basis = cross * ratio;
continue;
}
}
}
let child_known_dimensions = {
let mut ckd = child.size;
if child.align_self == AlignSelf::Stretch {
if constants.is_column && ckd.width.is_none() {
ckd.width = available_space.width.into_option();
}
if constants.is_row && ckd.height.is_none() {
ckd.height = available_space.height.into_option();
}
}
ckd
};
child.flex_basis = compute_node_layout(
tree,
child.node,
child_known_dimensions.maybe_min(child.max_size),
available_space,
RunMode::ComputeSize,
SizingMode::ContentSize,
)
.main(constants.dir)
.maybe_min(child.max_size.main(constants.dir));
}
for child in flex_items {
child.inner_flex_basis =
child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
let min_main = compute_node_layout(
tree,
child.node,
Size::NONE,
available_space,
RunMode::ComputeSize,
SizingMode::ContentSize,
)
.main(constants.dir)
.maybe_clamp(child.min_size.main(constants.dir), child.size.main(constants.dir))
.into();
child
.hypothetical_inner_size
.set_main(constants.dir, child.flex_basis.maybe_clamp(min_main, child.max_size.main(constants.dir)));
child.hypothetical_outer_size.set_main(
constants.dir,
child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
);
}
}
#[inline]
fn collect_flex_lines<'a>(
tree: &impl LayoutTree,
node: Node,
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
flex_items: &'a mut Vec<FlexItem>,
) -> Vec<FlexLine<'a>> {
let mut lines = crate::sys::new_vec_with_capacity(1);
if tree.style(node).flex_wrap == FlexWrap::NoWrap {
lines.push(FlexLine {
items: flex_items.as_mut_slice(),
container_main_size_contribution: 0.0,
cross_size: 0.0,
offset_cross: 0.0,
});
} else {
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;
if let AvailableSpace::Definite(main) = available_space.main(constants.dir) {
line_length > main && idx != 0
} else {
false
}
})
.map(|(idx, _)| idx)
.unwrap_or(flex_items.len());
let (items, rest) = flex_items.split_at_mut(index);
lines.push(FlexLine { items, container_main_size_contribution: 0.0, cross_size: 0.0, offset_cross: 0.0 });
flex_items = rest;
}
}
lines
}
#[inline]
fn resolve_flexible_lengths(
tree: &mut impl LayoutTree,
line: &mut FlexLine,
constants: &AlgoConstants,
original_gap: Size<f32>,
available_space: Size<AvailableSpace>,
) {
let total_original_main_axis_gap = sum_axis_gaps(original_gap.main(constants.dir), line.items.len());
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_original_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 = !growing;
for child in line.items.iter_mut() {
if constants.node_inner_size.main(constants.dir).is_none() && constants.is_row {
child.target_size.set_main(
constants.dir,
compute_node_layout(
tree,
child.node,
child.size.maybe_clamp(child.min_size, child.max_size),
available_space,
RunMode::ComputeSize,
SizingMode::ContentSize,
)
.main(constants.dir)
.maybe_clamp(child.min_size.main(constants.dir), child.max_size.main(constants.dir)),
);
} else {
child.target_size.set_main(constants.dir, child.hypothetical_inner_size.main(constants.dir));
}
child
.outer_target_size
.set_main(constants.dir, child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir));
let child_style = tree.style(child.node);
if (child_style.flex_grow == 0.0 && child_style.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 total_target_size = line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
line.container_main_size_contribution = total_target_size + total_original_main_axis_gap;
let used_space: f32 = total_main_axis_gap
+ line
.items
.iter()
.map(|child| {
child.margin.main_axis_sum(constants.dir)
+ if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
})
.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| {
child.margin.main_axis_sum(constants.dir)
+ if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
})
.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| {
let style = tree.style(item.node);
(flex_grow + style.flex_grow, flex_shrink + style.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 * (tree.style(child.node).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 * tree.style(child.node).flex_shrink).sum();
if sum_scaled_shrink_factor > 0.0 {
for child in &mut unfrozen {
let scaled_shrink_factor = child.inner_flex_basis * tree.style(child.node).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 min_main = if constants.is_row && !tree.needs_measure(child.node) {
compute_node_layout(
tree,
child.node,
Size::NONE,
available_space,
RunMode::ComputeSize,
SizingMode::ContentSize,
)
.width
.maybe_clamp(child.min_size.width, child.size.width)
.into()
} else {
child.min_size.main(constants.dir)
};
let max_main = child.max_size.main(constants.dir);
let clamped = child.target_size.main(constants.dir).maybe_clamp(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 LayoutTree,
line: &mut FlexLine,
constants: &AlgoConstants,
available_space: Size<AvailableSpace>,
) {
for child in line.items.iter_mut() {
let child_cross = child
.size
.cross(constants.dir)
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir));
child.hypothetical_inner_size.set_cross(
constants.dir,
compute_node_layout(
tree,
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() },
},
Size {
width: if constants.is_row {
constants.container_size.main(constants.dir).into()
} else {
available_space.width
},
height: if constants.is_row {
available_space.height
} else {
constants.container_size.main(constants.dir).into()
},
},
RunMode::ComputeSize,
SizingMode::ContentSize,
)
.cross(constants.dir)
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir)),
);
child.hypothetical_outer_size.set_cross(
constants.dir,
child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
);
}
}
#[inline]
fn calculate_children_base_lines(
tree: &mut impl LayoutTree,
node: Node,
node_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
flex_lines: &mut [FlexLine],
constants: &AlgoConstants,
) {
fn calc_baseline(db: &impl LayoutTree, node: Node, layout: &Layout) -> f32 {
if let Some(first_child) = db.children(node).next() {
let layout = db.layout(*first_child);
calc_baseline(db, *first_child, layout)
} else {
layout.size.height
}
}
for line in flex_lines {
for child in line.items.iter_mut() {
let preliminary_size = compute_node_layout(
tree,
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()
},
},
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()
},
},
RunMode::PeformLayout,
SizingMode::ContentSize,
);
child.baseline = calc_baseline(
tree,
child.node,
&Layout {
order: tree.children(node).position(|n| *n == child.node).unwrap() as u32,
size: preliminary_size,
location: Point::zero(),
},
);
}
}
}
#[inline]
fn calculate_cross_size(
tree: &mut impl LayoutTree,
flex_lines: &mut [FlexLine],
node_size: Size<Option<f32>>,
constants: &AlgoConstants,
) {
if flex_lines.len() == 1 && node_size.cross(constants.dir).is_some() {
flex_lines[0].cross_size =
(node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
.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| {
let child_style = tree.style(child.node);
if child.align_self == AlignSelf::Baseline
&& child_style.cross_margin_start(constants.dir) != LengthPercentageAuto::Auto
&& child_style.cross_margin_end(constants.dir) != LengthPercentageAuto::Auto
&& child_style.cross_size(constants.dir) == Dimension::Auto
{
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));
}
}
}
#[inline]
fn handle_align_content_stretch(
tree: &mut impl LayoutTree,
flex_lines: &mut [FlexLine],
node: Node,
node_size: Size<Option<f32>>,
constants: &AlgoConstants,
) {
let align_content = tree.style(node).align_content.unwrap_or(AlignContent::Stretch);
if align_content == AlignContent::Stretch && node_size.cross(constants.dir).is_some() {
let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
let inner_cross =
(node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
.unwrap_or(0.0);
if total_cross < inner_cross {
let remaining = inner_cross - 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: &mut impl LayoutTree, 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.style(child.node);
child.target_size.set_cross(
constants.dir,
if child.align_self == AlignSelf::Stretch
&& child_style.cross_margin_start(constants.dir) != LengthPercentageAuto::Auto
&& child_style.cross_margin_end(constants.dir) != LengthPercentageAuto::Auto
&& child_style.cross_size(constants.dir) == Dimension::Auto
{
(line_cross_size - child.margin.cross_axis_sum(constants.dir))
.maybe_clamp(child.min_size.cross(constants.dir), child.max_size.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(
tree: &mut impl LayoutTree,
flex_lines: &mut [FlexLine],
node: Node,
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() {
let child_style = tree.style(child.node);
if child_style.main_margin_start(constants.dir) == LengthPercentageAuto::Auto {
num_auto_margins += 1;
}
if child_style.main_margin_end(constants.dir) == LengthPercentageAuto::Auto {
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() {
let child_style = tree.style(child.node);
if child_style.main_margin_start(constants.dir) == LengthPercentageAuto::Auto {
if constants.is_row {
child.margin.left = margin;
} else {
child.margin.top = margin;
}
}
if child_style.main_margin_end(constants.dir) == LengthPercentageAuto::Auto {
if constants.is_row {
child.margin.right = margin;
} else {
child.margin.bottom = margin;
}
}
}
} else {
let num_items = line.items.len();
let layout_reverse = constants.dir.is_reverse();
let gap = constants.gap.main(constants.dir);
let justify_content_mode: JustifyContent =
tree.style(node).justify_content.unwrap_or(JustifyContent::Start);
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(tree: &mut impl LayoutTree, 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);
let child_style = tree.style(child.node);
if child_style.cross_margin_start(constants.dir) == LengthPercentageAuto::Auto
&& child_style.cross_margin_end(constants.dir) == LengthPercentageAuto::Auto
{
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_style.cross_margin_start(constants.dir) == LengthPercentageAuto::Auto {
if constants.is_row {
child.margin.top = free_space;
} else {
child.margin.left = free_space;
}
} else if child_style.cross_margin_end(constants.dir) == LengthPercentageAuto::Auto {
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: &mut FlexItem,
free_space: f32,
max_baseline: f32,
constants: &AlgoConstants,
) -> f32 {
match child.align_self {
AlignSelf::Start => {
if constants.is_wrap_reverse {
free_space
} else {
0.0
}
}
AlignSelf::End => {
if constants.is_wrap_reverse {
0.0
} else {
free_space
}
}
AlignSelf::Center => free_space / 2.0,
AlignSelf::Baseline => {
if constants.is_row {
max_baseline - child.baseline
} else {
if constants.is_wrap_reverse {
free_space
} else {
0.0
}
}
}
AlignSelf::Stretch => {
if constants.is_wrap_reverse {
free_space
} else {
0.0
}
}
}
}
#[inline]
#[must_use]
fn determine_container_cross_size(
flex_lines: &mut [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>();
constants.container_size.set_cross(
constants.dir,
node_size.cross(constants.dir).unwrap_or(
total_line_cross_size + total_cross_axis_gap + constants.padding_border.cross_axis_sum(constants.dir),
),
);
constants.inner_container_size.set_cross(
constants.dir,
constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
);
total_line_cross_size
}
#[inline]
fn align_flex_lines_per_align_content(
tree: &impl LayoutTree,
flex_lines: &mut [FlexLine],
node: Node,
constants: &AlgoConstants,
total_cross_size: f32,
) {
let num_lines = flex_lines.len();
let gap = constants.gap.cross(constants.dir);
let align_content_mode = tree.style(node).align_content.unwrap_or(AlignContent::Stretch);
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_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 LayoutTree,
node: Node,
item: &mut FlexItem,
total_offset_main: &mut f32,
total_offset_cross: f32,
line_offset_cross: f32,
container_size: Size<f32>,
direction: FlexDirection,
) {
let preliminary_size = compute_node_layout(
tree,
item.node,
item.target_size.map(|s| s.into()),
container_size.map(|s| s.into()),
RunMode::PeformLayout,
SizingMode::ContentSize,
);
let offset_main = *total_offset_main
+ item.offset_main
+ item.margin.main_start(direction)
+ (item.position.main_start(direction).unwrap_or(0.0) - item.position.main_end(direction).unwrap_or(0.0));
let offset_cross = total_offset_cross
+ item.offset_cross
+ line_offset_cross
+ item.margin.cross_start(direction)
+ (item.position.cross_start(direction).unwrap_or(0.0) - item.position.cross_end(direction).unwrap_or(0.0));
let order = tree.children(node).position(|n| *n == item.node).unwrap() as u32;
*tree.layout_mut(item.node) = Layout {
order,
size: preliminary_size,
location: Point {
x: if direction.is_row() { offset_main } else { offset_cross },
y: if direction.is_column() { offset_main } else { offset_cross },
},
};
*total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + preliminary_size.main(direction);
}
fn calculate_layout_line(
tree: &mut impl LayoutTree,
node: Node,
line: &mut FlexLine,
total_offset_cross: &mut f32,
container_size: Size<f32>,
padding_border: Rect<f32>,
direction: FlexDirection,
) {
let mut total_offset_main = padding_border.main_start(direction);
let line_offset_cross = line.offset_cross;
if direction.is_reverse() {
for item in line.items.iter_mut().rev() {
calculate_flex_item(
tree,
node,
item,
&mut total_offset_main,
*total_offset_cross,
line_offset_cross,
container_size,
direction,
);
}
} else {
for item in line.items.iter_mut() {
calculate_flex_item(
tree,
node,
item,
&mut total_offset_main,
*total_offset_cross,
line_offset_cross,
container_size,
direction,
);
}
}
*total_offset_cross += line_offset_cross + line.cross_size;
}
#[inline]
fn final_layout_pass(tree: &mut impl LayoutTree, node: Node, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
if constants.is_wrap_reverse {
for line in flex_lines.iter_mut().rev() {
calculate_layout_line(
tree,
node,
line,
&mut total_offset_cross,
constants.container_size,
constants.padding_border,
constants.dir,
);
}
} else {
for line in flex_lines.iter_mut() {
calculate_layout_line(
tree,
node,
line,
&mut total_offset_cross,
constants.container_size,
constants.padding_border,
constants.dir,
);
}
}
}
#[inline]
fn perform_absolute_layout_on_absolute_children(tree: &mut impl LayoutTree, node: Node, constants: &AlgoConstants) {
let candidates = tree
.children(node)
.cloned()
.enumerate()
.filter(|(_, child)| tree.style(*child).position_type == PositionType::Absolute)
.collect::<Vec<_>>();
for (order, child) in candidates {
let container_width = constants.container_size.width;
let container_height = constants.container_size.height;
let child_style = tree.style(child);
let child_position_start = child_style.position.left.maybe_resolve(container_width);
let child_margin_start = child_style.margin.left.maybe_resolve(container_width);
let start = child_position_start.maybe_add(child_margin_start);
let child_position_end = child_style.position.right.maybe_resolve(container_width);
let child_margin_end = child_style.margin.right.maybe_resolve(container_width);
let end = child_position_end.maybe_add(child_margin_end);
let child_position_top = child_style.position.top.maybe_resolve(container_height);
let child_margin_top = child_style.margin.top.maybe_resolve(container_height);
let top = child_position_top.maybe_add(child_margin_top);
let child_position_bottom = child_style.position.bottom.maybe_resolve(container_height);
let child_margin_bottom = child_style.margin.bottom.maybe_resolve(container_height);
let bottom = child_position_bottom.maybe_add(child_margin_bottom);
let (start_main, end_main) = if constants.is_row { (start, end) } else { (top, bottom) };
let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (start, end) };
let style_size = child_style.size.maybe_resolve(constants.container_size);
let min_size = child_style.min_size.maybe_resolve(constants.container_size);
let max_size = child_style.max_size.maybe_resolve(constants.container_size);
let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
if known_dimensions.width.is_none() && start.is_some() && end.is_some() {
known_dimensions.width = Some(container_width.maybe_sub(start).maybe_sub(end));
}
if known_dimensions.height.is_none() && top.is_some() && bottom.is_some() {
known_dimensions.height = Some(container_height.maybe_sub(top).maybe_sub(bottom));
}
let preliminary_size = compute_node_layout(
tree,
child,
known_dimensions,
Size {
width: AvailableSpace::Definite(container_width),
height: AvailableSpace::Definite(container_height),
},
RunMode::PeformLayout,
SizingMode::ContentSize,
);
let child_style = tree.style(child);
let free_main_space = constants.container_size.main(constants.dir)
- preliminary_size
.main(constants.dir)
.maybe_max(
child_style
.min_main_size(constants.dir)
.maybe_resolve(constants.node_inner_size.main(constants.dir)),
)
.maybe_min(
child_style
.max_main_size(constants.dir)
.maybe_resolve(constants.node_inner_size.main(constants.dir)),
);
let free_cross_space = constants.container_size.cross(constants.dir)
- preliminary_size
.cross(constants.dir)
.maybe_max(
child_style
.min_cross_size(constants.dir)
.maybe_resolve(constants.node_inner_size.cross(constants.dir)),
)
.maybe_min(
child_style
.max_cross_size(constants.dir)
.maybe_resolve(constants.node_inner_size.cross(constants.dir)),
);
let offset_main = if start_main.is_some() {
start_main.unwrap_or(0.0) + constants.border.main_start(constants.dir)
} else if end_main.is_some() {
free_main_space - end_main.unwrap_or(0.0) - constants.border.main_end(constants.dir)
} else {
match tree.style(node).justify_content.unwrap_or(JustifyContent::Start) {
JustifyContent::SpaceBetween | JustifyContent::Start | JustifyContent::Stretch => {
constants.padding_border.main_start(constants.dir)
}
JustifyContent::End => free_main_space - constants.padding_border.main_end(constants.dir),
JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
free_main_space / 2.0
}
}
};
let offset_cross = if start_cross.is_some() {
start_cross.unwrap_or(0.0) + constants.border.cross_start(constants.dir)
} else if end_cross.is_some() {
free_cross_space - end_cross.unwrap_or(0.0) - constants.border.cross_end(constants.dir)
} else {
match child_style.align_self.unwrap_or(constants.align_items) {
AlignSelf::Start => {
if constants.is_wrap_reverse {
free_cross_space - constants.padding_border.cross_end(constants.dir)
} else {
constants.padding_border.cross_start(constants.dir)
}
}
AlignSelf::End => {
if constants.is_wrap_reverse {
constants.padding_border.cross_start(constants.dir)
} else {
free_cross_space - constants.padding_border.cross_end(constants.dir)
}
}
AlignSelf::Center => free_cross_space / 2.0,
AlignSelf::Baseline => free_cross_space / 2.0, AlignSelf::Stretch => {
if constants.is_wrap_reverse {
free_cross_space - constants.padding_border.cross_end(constants.dir)
} else {
constants.padding_border.cross_start(constants.dir)
}
}
}
};
*tree.layout_mut(child) = Layout {
order: order as u32,
size: preliminary_size,
location: Point {
x: if constants.is_row { offset_main } else { offset_cross },
y: if constants.is_column { offset_main } else { offset_cross },
},
};
}
}
#[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(test)]
mod tests {
#![allow(clippy::redundant_clone)]
use crate::style_helpers::*;
use crate::{
math::MaybeMath,
prelude::{Rect, Size},
resolve::ResolveOrZero,
style::{FlexWrap, Style},
Taffy,
};
#[test]
fn correct_constants() {
let mut tree = Taffy::with_capacity(16);
let style = Style::default();
let node_id = tree.new_leaf(style.clone()).unwrap();
let node_size = Size::NONE;
let parent_size = Size::MAX_CONTENT;
let constants = super::compute_constants(tree.style(node_id).unwrap(), node_size, parent_size);
assert!(constants.dir == style.flex_direction);
assert!(constants.is_row == style.flex_direction.is_row());
assert!(constants.is_column == style.flex_direction.is_column());
assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
let margin = style.margin.resolve_or_zero(parent_size.into_options());
assert_eq!(constants.margin, margin);
let border = style.border.resolve_or_zero(parent_size.into_options());
assert_eq!(constants.border, border);
let padding = style.padding.resolve_or_zero(parent_size.into_options());
let padding_border = Rect {
left: padding.left + border.left,
right: padding.right + border.right,
top: padding.top + border.top,
bottom: padding.bottom + border.bottom,
};
assert_eq!(constants.padding_border, padding_border);
let inner_size = Size {
width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
};
assert_eq!(constants.node_inner_size, inner_size);
assert_eq!(constants.container_size, Size::zero());
assert_eq!(constants.inner_container_size, Size::zero());
}
}