in GaiaXStretch/src/algo.rs [168:1162]
fn compute_internal(
&mut self,
current_node: NodeId,
current_node_size: Size<Number>,
parent_node_size: Size<Number>,
perform_layout: bool,
) -> ComputeResult {
self.nodes[current_node].is_dirty = false;
// First we check if we have a result for the given input
if let Some(ref cache) = self.nodes[current_node].layout_cache {
if cache.perform_layout || !perform_layout {
let width_compatible = if let Number::Defined(width) = current_node_size.width {
sys::abs(width - cache.result.size.width) < f32::EPSILON
} else {
cache.node_size.width.is_undefined()
};
let height_compatible = if let Number::Defined(height) = current_node_size.height {
sys::abs(height - cache.result.size.height) < f32::EPSILON
} else {
cache.node_size.height.is_undefined()
};
if width_compatible && height_compatible {
return cache.result.clone();
}
if cache.node_size == current_node_size && cache.parent_size == parent_node_size {
return cache.result.clone();
}
}
}
// Define some general constants we will need for the remainder
// of the algorithm.
// 定义一些常用的变量,方便后边算法的使用
let current_node_style: &Style = &self.nodes[current_node].style;
let current_node_direction: FlexDirection = current_node_style.flex_direction;
let current_node_is_row: bool = current_node_direction.is_row();
let current_node_is_column: bool = current_node_direction.is_column();
let current_node_is_wrap_reverse: bool = current_node_style.flex_wrap == FlexWrap::WrapReverse;
let current_node_is_flex_grow: bool = current_node_style.flex_grow != 0.0;
let current_node_margin: Rect<f32> =
current_node_style.margin.map(|n: Dimension| n.resolve(parent_node_size.width).or_else(0.0));
let current_node_padding: Rect<f32> =
current_node_style.padding.map(|n: Dimension| n.resolve(parent_node_size.width).or_else(0.0));
let current_node_border: Rect<f32> =
current_node_style.border.map(|n: Dimension| n.resolve(parent_node_size.width).or_else(0.0));
// 内边距+边框尺寸
let current_node_padding_border = Rect {
start: current_node_padding.start + current_node_border.start,
end: current_node_padding.end + current_node_border.end,
top: current_node_padding.top + current_node_border.top,
bottom: current_node_padding.bottom + current_node_border.bottom,
};
// 节点的内部尺寸 = 节点宽高减去内边距和边框
// 可用于展示内容的区域
let current_node_inner_size = Size {
width: current_node_size.width - current_node_padding_border.horizontal(),
height: current_node_size.height - current_node_padding_border.vertical(),
};
// 容器尺寸
let mut current_node_container_size: Size<f32> = Size::zero();
// 内部容器尺寸
let mut current_node_inner_container_size: Size<f32> = Size::zero();
// If this is a leaf node we can skip a lot of this function in some cases
// 如果是个叶子节点,可以省略许多逻辑处理
if self.children[current_node].is_empty() {
// 如果叶子节点宽高已经定义了,那么直接返回结果
if current_node_size.width.is_defined() && current_node_size.height.is_defined() {
return ComputeResult { size: current_node_size.map(|s| s.or_else(0.0)) };
}
// 如果叶子节点设置了测量方法,那么使用测量方法返回结果
if let Some(ref measure) = self.nodes[current_node].measure {
let result = match measure {
MeasureFunc::Raw(measure) => ComputeResult { size: measure(current_node_size) },
#[cfg(any(feature = "std", feature = "alloc"))]
MeasureFunc::Boxed(measure) => ComputeResult { size: measure(current_node_size) },
};
self.nodes[current_node].layout_cache = Some(result::Cache {
node_size: current_node_size,
parent_size: parent_node_size,
perform_layout,
result: result.clone(),
});
return result;
}
// 此处有疑问? 为什么是节点尺寸 + 内边距边框尺寸
// 按照合理来说,应该就是节点尺寸
return ComputeResult {
size: Size {
width: current_node_size.width.or_else(0.0) + current_node_padding_border.horizontal(),
height: current_node_size.height.or_else(0.0) + current_node_padding_border.vertical(),
},
};
}
// 9.2. Line Length Determination
// 9.2 确定行的长度
// 1. Generate anonymous flex items as described in §4 Flex Items.
// 1. 根据孩子节点生成匿名的flex项目。
let mut current_node_child_flex_items: sys::Vec<FlexItem> =
self.get_current_node_flex_items(current_node, current_node_inner_size);
// 2. Determine the available main and cross space for the flex items.
// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in
// that dimension is that constraint; otherwise, subtract the flex container’s
// margin, border, and padding from the space available to the flex container
// in that dimension and use that value. This might result in an infinite value.
// 2. 确定flex项目的可用主轴空间和交叉轴空间。
// 对于每个flex项目的尺寸:
// 1. 如果flex容器的尺寸是一个确定的大小,则使用它。
// 2. 如果flex容器的尺寸是在最小或最大约束下的,则可用空间的尺寸就是它的约束。
// 3. 孩子的flex项目的可用空间,是其父flex容器的尺寸减去外边距、边框和内边距后的值。
// 当前父节点下所有伸缩项的可用空间
let current_node_available_space = Size {
width: current_node_size.width.or_else(parent_node_size.width - current_node_margin.horizontal())
- current_node_padding_border.horizontal(),
height: current_node_size.height.or_else(parent_node_size.height - current_node_margin.vertical())
- current_node_padding_border.vertical(),
};
// 当前父节点下是否存在有基线对齐样式的flex项目
let has_baseline_child = current_node_child_flex_items.iter().any(|child| {
self.nodes[child.node].style.align_self(&self.nodes[current_node].style) == AlignSelf::Baseline
});
// TODO - this does not follow spec. See commented out code below
// 3. Determine the flex base size and hypothetical main size of each item:
// flex_basis: 主轴方向上的初始大小 https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-basis
// 3.1 确定每个flex项目的flex基准值。
self.flex_items_flex_basis(
current_node,
current_node_direction,
current_node_is_row,
current_node_is_column,
current_node_inner_size,
&mut current_node_child_flex_items,
current_node_available_space,
);
// The hypothetical main size is the item’s flex base size clamped according to its
// used min and max main sizes (and flooring the content box size at zero).
// 假设的主轴尺寸是一个被最小主轴尺寸和最大主轴尺寸裁剪过的。
// 3.2 确定每个flex项目假设的主轴尺寸。
self.flex_items_hypothetical_main_size(
current_node_direction,
&mut current_node_child_flex_items,
current_node_available_space,
);
// 9.3. Main Size Determination
// 9.3 确定flex项目的主轴尺寸
// 5. Collect flex items into flex lines:
// - If the flex container is single-line, collect all the flex items into
// a single flex line.
// - Otherwise, starting from the first uncollected item, collect consecutive
// items one by one until the first time that the next collected item would
// not fit into the flex container’s inner main size (or until a forced break
// is encountered, see §10 Fragmenting Flex Layout). If the very first
// uncollected item wouldn’t fit, collect just it into the line.
//
// For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.)
// Repeat until all flex items have been collected into flex lines
//
// Note that the "collect as many" line will collect zero-sized flex items onto
// the end of the previous line even if the last non-zero item exactly "filled up" the line.
// 5. 将flex元素收集到flex项目行中:
// - 如果flex容器是单行的,则将所有flex项目项到一个flex项目行中。
let mut flex_lines: sys::Vec<_> = {
let mut lines = sys::new_vec_with_capacity(1);
let style: Style = self.nodes[current_node].style;
if style.flex_wrap == FlexWrap::NoWrap {
lines.push(FlexLine {
items: current_node_child_flex_items.as_mut_slice(),
cross_size: 0.0,
offset_cross: 0.0,
});
} else {
let mut flex_items = &mut current_node_child_flex_items[..];
while !flex_items.is_empty() {
let mut line_length = 0.0;
let index = flex_items
.iter()
.enumerate()
.find(|&(idx, ref child)| {
line_length += child.hypothetical_outer_size.main(current_node_direction);
if let Defined(main) = current_node_available_space.main(current_node_direction) {
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, cross_size: 0.0, offset_cross: 0.0 });
flex_items = rest;
}
}
lines
};
// 6. Resolve the flexible lengths of all the flex items to find their used main size.
// See §9.7 Resolving Flexible Lengths.
// 6. 解析所有flex项目的flex长度,以找到它们使用的主轴尺寸。参见§9.7解析flex项目项的长度。
// 9.7 Resolving Flexible Lengths
// 9.7 解析确定flex项目的长度
self.flex_lines_main_size(
parent_node_size,
current_node_direction,
current_node_is_row,
current_node_is_column,
current_node_inner_size,
current_node_available_space,
&mut flex_lines,
);
// Not part of the spec from what i can see but seems correct
// 设置flex项目行容器的主轴尺寸
Forest::container_main_size(
current_node_size,
current_node_direction,
current_node_padding_border,
&mut current_node_container_size,
&mut current_node_inner_container_size,
current_node_available_space,
&mut flex_lines,
);
// 9.4. Cross Size Determination
// 确定交叉轴尺寸
// 7. Determine the hypothetical cross size of each item by performing layout with the
// used main size and the available space, treating auto as fit-content.
// 执行布局测量结合主轴尺寸和可用空间确定猜测的交叉轴尺寸
self.flex_items_hypothetical_cross(
current_node,
current_node_size,
current_node_direction,
current_node_is_row,
current_node_container_size,
current_node_available_space,
&mut flex_lines,
);
// TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
// TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
fn calc_baseline(db: &Forest, node: NodeId, layout: &result::Layout) -> f32 {
if db.children[node].is_empty() {
layout.size.height
} else {
let child = db.children[node][0];
calc_baseline(db, child, &db.nodes[child].layout)
}
}
if has_baseline_child {
for line in &mut flex_lines {
for target in line.items.iter_mut() {
let child: &mut FlexItem = target;
let result = self.compute_internal(
child.node,
Size {
width: if current_node_is_row {
child.target_size.width.into()
} else {
child.hypothetical_inner_size.width.into()
},
height: if current_node_is_row {
child.hypothetical_inner_size.height.into()
} else {
child.target_size.height.into()
},
},
Size {
width: if current_node_is_row {
current_node_container_size.width.into()
} else {
current_node_size.width
},
height: if current_node_is_row {
current_node_size.height
} else {
current_node_container_size.height.into()
},
},
true,
);
child.baseline = calc_baseline(
self,
child.node,
&result::Layout {
order: self.children[current_node].iter().position(|n| *n == child.node).unwrap() as u32,
size: result.size,
location: Point::zero(),
},
);
}
}
}
// 8. Calculate the cross size of each flex line.
// If the flex container is single-line and has a definite cross size, the cross size
// of the flex line is the flex container’s inner cross size. Otherwise, for each flex line:
//
// If the flex container is single-line, then clamp the line’s cross-size to be within
// the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition
// of min/max-width/height applied more generally, this behavior would fall out automatically.
// 计算flex项目行容器的交叉轴尺寸
// 如果flex项目行容器是个单行的,并且有确定的交叉轴尺寸,这个尺寸就是flex项目行容器的内部交叉轴尺寸。
//
if flex_lines.len() == 1 && current_node_size.cross(current_node_direction).is_defined() {
let size_cross = current_node_size.cross(current_node_direction);
let padding_border_cross = current_node_padding_border.cross(current_node_direction);
flex_lines[0].cross_size = (size_cross - padding_border_cross).or_else(0.0);
} else {
for line in &mut flex_lines {
// 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
// align-self is baseline, and whose cross-axis margins are both non-auto. Find the
// largest of the distances between each item’s baseline and its hypothetical outer
// cross-start edge, and the largest of the distances between each item’s baseline
// and its hypothetical outer cross-end edge, and sum these two values.
// 2. Among all the items not collected by the previous step, find the largest
// outer hypothetical cross size.
// 3. The used cross-size of the flex line is the largest of the numbers found in the
// previous two steps and zero.
let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
let mut is_have_aspect_ratio_item: bool = false;
let mut is_have_point_size_item: bool = false;
let final_cross_size: f64 = line
.items
.iter()
.map(|child: &FlexItem| {
let child_style: &Style = &self.nodes[child.node].style;
if child_style.aspect_ratio.is_defined() {
is_have_aspect_ratio_item = true;
}
if child_style.size.width.is_defined() {
is_have_point_size_item = true;
}
if child_style.align_self(&self.nodes[current_node].style) == AlignSelf::Baseline
&& child_style.cross_margin_start(current_node_direction) != Dimension::Auto
&& child_style.cross_margin_end(current_node_direction) != Dimension::Auto
&& child_style.cross_size(current_node_direction) == Dimension::Auto
{
max_baseline - child.baseline + child.hypothetical_outer_size.cross(current_node_direction)
} else {
child.hypothetical_outer_size.cross(current_node_direction)
}
})
.fold(0.0, |acc: f64, x: f32| acc.max(x as f64));
// fix: aspect_ratio_multi_4_aspect_ratio_flex_grow_point_width
// 修复:flex-grow与aspect-ratio嵌套组合使用时,cross-size受到同级孩子的固定宽度影响,导致flex-grow计算错误的问题。
if current_node_is_column
&& current_node_is_flex_grow
&& is_have_aspect_ratio_item
&& is_have_point_size_item
{
line.cross_size = 0.0
} else {
line.cross_size = final_cross_size as f32;
}
}
}
// 9. Handle 'align-content: stretch'. If the flex container has a definite cross size,
// align-content is stretch, and the sum of the flex lines' cross sizes is less than
// the flex container’s inner cross size, increase the cross size of each flex line
// by equal amounts such that the sum of their cross sizes exactly equals the
// flex container’s inner cross size.
// CSS 的 align-content 属性设置了浏览器如何沿着弹性盒子布局的纵轴和网格布局的主轴在内容项之间和周围分配空间。
// 处理'align-content:stretch'
// align-content: stretch; 均匀分布项目 拉伸‘自动’-大小的项目以充满容器
if self.nodes[current_node].style.align_content == AlignContent::Stretch
&& current_node_size.cross(current_node_direction).is_defined()
{
let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
let inner_cross = (current_node_size.cross(current_node_direction)
- current_node_padding_border.cross(current_node_direction))
.or_else(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);
}
}
// 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
// note the cross size of the line they’re in as the item’s strut size, and restart
// layout from the beginning.
//
// In this second layout round, when collecting items into lines, treat the collapsed
// items as having zero main size. For the rest of the algorithm following that step,
// ignore the collapsed items entirely (as if they were display:none) except that after
// calculating the cross size of the lines, if any line’s cross size is less than the
// largest strut size among all the collapsed items in the line, set its cross size to
// that strut size.
//
// Skip this step in the second layout round.
// TODO implement once (if ever) we support visibility:collapse
// 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch,
// its computed cross size property is auto, and neither of its cross-axis margins are auto,
// the used outer cross size is the used cross size of its flex line, clamped according to
// the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s
// hypothetical cross size.
//
// If the flex item has align-self: stretch, redo layout for its contents, treating this
// used size as its definite cross size so that percentage-sized children can be resolved.
//
// Note that this step does not affect the main size of the flex item, even if it has an
// intrinsic aspect ratio.
for line in &mut flex_lines {
let line_cross_size: f32 = line.cross_size;
for target in line.items.iter_mut() {
let child: &mut FlexItem = target;
let parent_style: &Style = &self.nodes[current_node].style;
let child_style: &Style = &self.nodes[child.node].style;
let child_cross_size = Forest::get_cross_size(
current_node_size,
parent_node_size,
current_node_direction,
current_node_is_row,
line_cross_size,
child,
parent_style,
child_style,
);
child.target_size.set_cross(current_node_direction, child_cross_size);
let outer_size_cross =
child.target_size.cross(current_node_direction) + child.margin.cross(current_node_direction);
child.outer_target_size.set_cross(current_node_direction, outer_size_cross);
}
}
// 9.5. Main-Axis Alignment
// 12. Distribute any remaining free space. For each flex line:
// 1. If the remaining free space is positive and at least one main-axis margin on this
// line is auto, distribute the free space equally among these margins. Otherwise,
// set all auto margins to zero.
// 2. Align the items along the main-axis per justify-content.
for line in &mut flex_lines {
let used_space: f32 =
line.items.iter().map(|child| child.outer_target_size.main(current_node_direction)).sum();
let free_space = current_node_inner_container_size.main(current_node_direction) - used_space;
let mut num_auto_margins = 0;
for child in line.items.iter_mut() {
let child_style = &self.nodes[child.node].style;
if child_style.main_margin_start(current_node_direction) == Dimension::Auto {
num_auto_margins += 1;
}
if child_style.main_margin_end(current_node_direction) == Dimension::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: &Style = &self.nodes[child.node].style;
if child_style.main_margin_start(current_node_direction) == Dimension::Auto {
if current_node_is_row {
child.margin.start = margin;
} else {
child.margin.top = margin;
}
}
if child_style.main_margin_end(current_node_direction) == Dimension::Auto {
if current_node_is_row {
child.margin.end = margin;
} else {
child.margin.bottom = margin;
}
}
}
} else {
let num_items = line.items.len();
let layout_reverse = current_node_direction.is_reverse();
let justify_item = |(i, child): (usize, &mut FlexItem)| {
let is_first = i == 0;
child.offset_main = match self.nodes[current_node].style.justify_content {
JustifyContent::FlexStart => {
if layout_reverse && is_first {
free_space
} else {
0.0
}
}
JustifyContent::Center => {
if is_first {
free_space / 2.0
} else {
0.0
}
}
JustifyContent::FlexEnd => {
if is_first && !layout_reverse {
free_space
} else {
0.0
}
}
JustifyContent::SpaceBetween => {
if is_first {
0.0
} else {
free_space / (num_items - 1) as f32
}
}
JustifyContent::SpaceAround => {
if is_first {
(free_space / num_items as f32) / 2.0
} else {
free_space / num_items as f32
}
}
JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
};
};
if layout_reverse {
line.items.iter_mut().rev().enumerate().for_each(justify_item);
} else {
line.items.iter_mut().enumerate().for_each(justify_item);
}
}
}
// 9.6. Cross-Axis Alignment
// 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins:
// - If its outer cross size (treating those auto margins as zero) is less than the
// cross size of its flex line, distribute the difference in those sizes equally
// to the auto margins.
// - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis)
// is auto, set it to zero. Set the opposite margin so that the outer cross size of the
// item equals the cross size of its flex line.
for line in &mut 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(current_node_direction);
let child_style = &self.nodes[child.node].style;
if child_style.cross_margin_start(current_node_direction) == Dimension::Auto
&& child_style.cross_margin_end(current_node_direction) == Dimension::Auto
{
if current_node_is_row {
child.margin.top = free_space / 2.0;
child.margin.bottom = free_space / 2.0;
} else {
child.margin.start = free_space / 2.0;
child.margin.end = free_space / 2.0;
}
} else if child_style.cross_margin_start(current_node_direction) == Dimension::Auto {
if current_node_is_row {
child.margin.top = free_space;
} else {
child.margin.start = free_space;
}
} else if child_style.cross_margin_end(current_node_direction) == Dimension::Auto {
if current_node_is_row {
child.margin.bottom = free_space;
} else {
child.margin.end = free_space;
}
} else {
// 14. Align all flex items along the cross-axis per align-self, if neither of the item’s
// cross-axis margins are auto.
child.offset_cross = match child_style.align_self(&self.nodes[current_node].style) {
AlignSelf::Auto => 0.0, // Should never happen
AlignSelf::FlexStart => {
if current_node_is_wrap_reverse {
free_space
} else {
0.0
}
}
AlignSelf::FlexEnd => {
if current_node_is_wrap_reverse {
0.0
} else {
free_space
}
}
AlignSelf::Center => free_space / 2.0,
AlignSelf::Baseline => {
if current_node_is_row {
max_baseline - child.baseline
} else {
// baseline alignment only makes sense if the direction is row
// we treat it as flex-start alignment in columns.
if current_node_is_wrap_reverse {
free_space
} else {
0.0
}
}
}
AlignSelf::Stretch => {
if current_node_is_wrap_reverse {
free_space
} else {
0.0
}
}
};
}
}
}
// 15. Determine the flex container’s used cross size:
// - If the cross size property is a definite size, use that, clamped by the used
// min and max cross sizes of the flex container.
// - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used
// min and max cross sizes of the flex container.
// 15 确定flex容器交叉轴的尺寸
// - 如果交叉轴属性是一个确定的值,就使用它,同是该值需要被交叉轴最小值和交叉轴最大值约束。
// - 否则,使用flex项目行的交叉轴尺寸,同是该值需要被交叉轴最小值和交叉轴最大值约束。
let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
current_node_container_size.set_cross(
current_node_direction,
current_node_size
.cross(current_node_direction)
.or_else(total_cross_size + current_node_padding_border.cross(current_node_direction)),
);
current_node_inner_container_size.set_cross(
current_node_direction,
current_node_container_size.cross(current_node_direction)
- current_node_padding_border.cross(current_node_direction),
);
// We have the container size. If our caller does not care about performing
// layout we are done now.
if !perform_layout {
let result = ComputeResult { size: current_node_container_size };
self.nodes[current_node].layout_cache = Some(result::Cache {
node_size: current_node_size,
parent_size: parent_node_size,
perform_layout,
result: result.clone(),
});
return result;
}
// 16. Align all flex lines per align-content.
let free_space = current_node_inner_container_size.cross(current_node_direction) - total_cross_size;
let num_lines = flex_lines.len();
let align_line = |(i, line): (usize, &mut FlexLine)| {
let is_first = i == 0;
line.offset_cross = match self.nodes[current_node].style.align_content {
AlignContent::FlexStart => {
if is_first && current_node_is_wrap_reverse {
free_space
} else {
0.0
}
}
AlignContent::FlexEnd => {
if is_first && !current_node_is_wrap_reverse {
free_space
} else {
0.0
}
}
AlignContent::Center => {
if is_first {
free_space / 2.0
} else {
0.0
}
}
AlignContent::Stretch => 0.0,
AlignContent::SpaceBetween => {
if is_first {
0.0
} else {
free_space / (num_lines - 1) as f32
}
}
AlignContent::SpaceAround => {
if is_first {
(free_space / num_lines as f32) / 2.0
} else {
free_space / num_lines as f32
}
}
};
};
if current_node_is_wrap_reverse {
flex_lines.iter_mut().rev().enumerate().for_each(align_line);
} else {
flex_lines.iter_mut().enumerate().for_each(align_line);
}
// Do a final layout pass and gather the resulting layouts
{
let mut total_offset_cross = current_node_padding_border.cross_start(current_node_direction);
let layout_line = |line: &mut FlexLine| {
let mut total_offset_main = current_node_padding_border.main_start(current_node_direction);
let line_offset_cross = line.offset_cross;
let layout_item = |child: &mut FlexItem| {
let result = self.compute_internal(
child.node,
child.target_size.map(|s| s.into()),
current_node_container_size.map(|s| s.into()),
true,
);
let offset_main = total_offset_main
+ child.offset_main
+ child.margin.main_start(current_node_direction)
+ (child.position.main_start(current_node_direction).or_else(0.0)
- child.position.main_end(current_node_direction).or_else(0.0));
let offset_cross = total_offset_cross
+ child.offset_cross
+ line_offset_cross
+ child.margin.cross_start(current_node_direction)
+ (child.position.cross_start(current_node_direction).or_else(0.0)
- child.position.cross_end(current_node_direction).or_else(0.0));
self.nodes[child.node].layout = result::Layout {
order: self.children[current_node].iter().position(|n| *n == child.node).unwrap() as u32,
size: result.size,
location: Point {
x: if current_node_is_row { offset_main } else { offset_cross },
y: if current_node_is_column { offset_main } else { offset_cross },
},
};
total_offset_main += child.offset_main
+ child.margin.main(current_node_direction)
+ result.size.main(current_node_direction);
};
if current_node_direction.is_reverse() {
line.items.iter_mut().rev().for_each(layout_item);
} else {
line.items.iter_mut().for_each(layout_item);
}
total_offset_cross += line_offset_cross + line.cross_size;
};
if current_node_is_wrap_reverse {
flex_lines.iter_mut().rev().for_each(layout_line);
} else {
flex_lines.iter_mut().for_each(layout_line);
}
}
// Before returning we perform absolute layout on all absolutely positioned children
{
// TODO: remove number of Vec<_> generated
let candidates = self.children[current_node]
.iter()
.cloned()
.enumerate()
.filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute)
.collect::<sys::Vec<_>>();
for (order, child) in candidates {
let container_width = current_node_container_size.width.into();
let container_height = current_node_container_size.height.into();
let child_style: Style = self.nodes[child].style;
let start = child_style.position.start.resolve(container_width)
+ child_style.margin.start.resolve(container_width);
let end =
child_style.position.end.resolve(container_width) + child_style.margin.end.resolve(container_width);
let top = child_style.position.top.resolve(container_height)
+ child_style.margin.top.resolve(container_height);
let bottom = child_style.position.bottom.resolve(container_height)
+ child_style.margin.bottom.resolve(container_height);
let (start_main, end_main) = if current_node_is_row { (start, end) } else { (top, bottom) };
let (start_cross, end_cross) = if current_node_is_row { (top, bottom) } else { (start, end) };
let mut width = child_style
.size
.width
.resolve(container_width)
.maybe_max(child_style.min_size.width.resolve(container_width))
.maybe_min(child_style.max_size.width.resolve(container_width))
.or_else(if start.is_defined() && end.is_defined() {
container_width - start - end
} else {
Undefined
});
let mut height = child_style
.size
.height
.resolve(container_height)
.maybe_max(child_style.min_size.height.resolve(container_height))
.maybe_min(child_style.max_size.height.resolve(container_height))
.or_else(if top.is_defined() && bottom.is_defined() {
container_height - top - bottom
} else {
Undefined
});
// fix: aspect_ratio_absolute_layout_width_defined
if child_style.aspect_ratio.is_defined() && !height.is_defined() && width.is_defined() {
height = Number::Defined(width.or_else(0.0) / child_style.aspect_ratio.or_else(0.0));
}
// fix: aspect_ratio_absolute_layout_height_defined
else if child_style.aspect_ratio.is_defined() && height.is_defined() && !width.is_defined() {
width = Number::Defined(height.or_else(0.0) * child_style.aspect_ratio.or_else(0.0));
}
let result = self.compute_internal(
child,
Size { width, height },
Size { width: container_width, height: container_height },
true,
);
let free_main_space = current_node_container_size.main(current_node_direction)
- result
.size
.main(current_node_direction)
.maybe_max(
child_style
.min_main_size(current_node_direction)
.resolve(current_node_inner_size.main(current_node_direction)),
)
.maybe_min(
child_style
.max_main_size(current_node_direction)
.resolve(current_node_inner_size.main(current_node_direction)),
);
let free_cross_space = current_node_container_size.cross(current_node_direction)
- result
.size
.cross(current_node_direction)
.maybe_max(
child_style
.min_cross_size(current_node_direction)
.resolve(current_node_inner_size.cross(current_node_direction)),
)
.maybe_min(
child_style
.max_cross_size(current_node_direction)
.resolve(current_node_inner_size.cross(current_node_direction)),
);
let offset_main = if start_main.is_defined() {
start_main.or_else(0.0) + current_node_border.main_start(current_node_direction)
} else if end_main.is_defined() {
free_main_space - end_main.or_else(0.0) - current_node_border.main_end(current_node_direction)
} else {
match self.nodes[current_node].style.justify_content {
JustifyContent::SpaceBetween | JustifyContent::FlexStart => {
current_node_padding_border.main_start(current_node_direction)
}
JustifyContent::FlexEnd => {
free_main_space - current_node_padding_border.main_end(current_node_direction)
}
JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
free_main_space / 2.0
}
}
};
let offset_cross = if start_cross.is_defined() {
start_cross.or_else(0.0) + current_node_border.cross_start(current_node_direction)
} else if end_cross.is_defined() {
free_cross_space - end_cross.or_else(0.0) - current_node_border.cross_end(current_node_direction)
} else {
match child_style.align_self(&self.nodes[current_node].style) {
AlignSelf::Auto => 0.0, // Should never happen
AlignSelf::FlexStart => {
if current_node_is_wrap_reverse {
free_cross_space - current_node_padding_border.cross_end(current_node_direction)
} else {
current_node_padding_border.cross_start(current_node_direction)
}
}
AlignSelf::FlexEnd => {
if current_node_is_wrap_reverse {
current_node_padding_border.cross_start(current_node_direction)
} else {
free_cross_space - current_node_padding_border.cross_end(current_node_direction)
}
}
AlignSelf::Center => free_cross_space / 2.0,
AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
AlignSelf::Stretch => {
if current_node_is_wrap_reverse {
free_cross_space - current_node_padding_border.cross_end(current_node_direction)
} else {
current_node_padding_border.cross_start(current_node_direction)
}
}
}
};
self.nodes[child].layout = result::Layout {
order: order as u32,
size: result.size,
location: Point {
x: if current_node_is_row { offset_main } else { offset_cross },
y: if current_node_is_column { offset_main } else { offset_cross },
},
};
}
}
fn hidden_layout(nodes: &mut [NodeData], children: &[sys::ChildrenVec<NodeId>], node: NodeId, order: u32) {
nodes[node].layout = result::Layout { order, size: Size::zero(), location: Point::zero() };
for (order, child) in children[node].iter().enumerate() {
hidden_layout(nodes, children, *child, order as _);
}
}
for (order, child) in self.children[current_node].iter().enumerate() {
if self.nodes[*child].style.display == Display::None {
hidden_layout(&mut self.nodes, &self.children, *child, order as _);
}
}
let result = ComputeResult { size: current_node_container_size };
self.nodes[current_node].layout_cache = Some(result::Cache {
node_size: current_node_size,
parent_size: parent_node_size,
perform_layout,
result: result.clone(),
});
result
}