pub(crate) mod common;
pub(crate) mod leaf;
pub(crate) mod oof;
#[cfg(feature = "block_layout")]
pub(crate) mod block;
#[cfg(feature = "float_layout")]
pub(crate) mod float;
#[cfg(feature = "flexbox")]
pub(crate) mod flexbox;
#[cfg(feature = "grid")]
pub(crate) mod grid;
pub use leaf::compute_leaf_layout;
pub use oof::{compute_oof_layout, compute_oof_layout_for_area, resolve_static_offset, OofLayoutResult};
#[cfg(feature = "block_layout")]
pub use self::block::{compute_block_align_content_offset, compute_block_layout, BlockContext, BlockFormattingContext};
#[cfg(feature = "flexbox")]
pub use self::flexbox::compute_flexbox_layout;
#[cfg(feature = "grid")]
pub use self::grid::compute_grid_layout;
#[cfg(feature = "float_layout")]
pub use self::float::{BfcSlot, ContentSlot, FloatContext, FloatIntrinsicWidthCalculator};
use crate::geometry::{Line, Point, Size};
use crate::style::{AvailableSpace, ContainingBlockClaims, CoreStyle, Overflow, Position};
use crate::tree::{
AxisStaticAlign, AxisStaticEdge, AxisStaticPosition, Layout, LayoutInput, LayoutOutput, LayoutPartialTree,
LayoutPartialTreeExt, NodeId, OofCandidate, OofCandidates, RequestedAxis, RoundTree, RunMode, SizingMode,
};
use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
use crate::util::sys::{round, Vec};
use crate::util::ResolveOrZero;
use crate::{CacheTree, MaybeMath, MaybeResolve};
pub fn compute_root_layout(
tree: &mut (impl crate::tree::LayoutContainingBlock + CacheTree),
root: NodeId,
available_space: Size<AvailableSpace>,
) {
let style = tree.get_core_container_style(root);
let position = style.position();
let direction = style.direction();
drop(style);
let icb_size = available_space.into_options();
let oof_root_area = match (position.is_out_of_flow(), icb_size.width, icb_size.height) {
(true, Some(width), Some(height)) => Some(Size { width, height }),
_ => None,
};
let mut root_is_cached = false;
let (layout, candidates) = if let Some(area_size) = oof_root_area {
let (start, end) = if direction.is_rtl() { (area_size.width, area_size.width) } else { (0.0, 0.0) };
let candidate = OofCandidate {
node: root,
order: 0,
position,
static_position: Point {
x: AxisStaticPosition {
area: Line { start, end },
align: AxisStaticAlign::from_keyword(if direction.is_rtl() {
AxisStaticEdge::End
} else {
AxisStaticEdge::Start
}),
},
y: AxisStaticPosition::from_edge(0.0, AxisStaticEdge::Start),
},
};
let oof::OofBoxLayout { layout, surfaced, .. } = oof::layout_oof_box(
tree,
candidate,
area_size,
Point::ZERO,
direction,
Point { x: Overflow::Visible, y: Overflow::Visible },
#[cfg(feature = "grid")]
None,
|tree, inputs| root_is_cached = tree.cache_get(root, inputs).is_some(),
);
(layout, surfaced)
} else {
compute_in_flow_root_layout(tree, root, available_space, &mut root_is_cached)
};
if !candidates.is_empty() {
let overflow = tree.get_core_container_style(root).overflow();
let area_inset = layout.border
+ crate::geometry::Rect {
left: 0.0,
right: layout.scrollbar_size.width,
top: 0.0,
bottom: layout.scrollbar_size.height,
};
let root_padding_box_size =
layout.size - Size { width: area_inset.horizontal_axis_sum(), height: area_inset.vertical_axis_sum() };
let (area_width, area_x) = match icb_size.width {
Some(width) => ((width - layout.scrollbar_size.width).max(0.0), -layout.location.x),
None => (root_padding_box_size.width, area_inset.left),
};
let (area_height, area_y) = match icb_size.height {
Some(height) => ((height - layout.scrollbar_size.height).max(0.0), -layout.location.y),
None => (root_padding_box_size.height, area_inset.top),
};
let area_size = Size { width: area_width, height: area_height };
let area_offset = Point { x: area_x, y: area_y };
let mut hoisted: Vec<NodeId> = Vec::new();
let mut unclaimed = OofCandidates::new();
oof::perform_oof_layout(
tree,
root,
candidates.as_slice(),
area_size,
area_offset,
direction,
ContainingBlockClaims::ALL,
overflow,
&mut hoisted,
&mut unclaimed,
);
debug_assert!(unclaimed.is_empty(), "the root positioning pass must claim all remaining candidates");
if !root_is_cached {
tree.add_hoisted_children(root, &hoisted);
}
}
}
#[inline(always)]
fn compute_in_flow_root_layout(
tree: &mut (impl crate::tree::LayoutContainingBlock + CacheTree),
root: NodeId,
available_space: Size<AvailableSpace>,
root_is_cached: &mut bool,
) -> (Layout, OofCandidates) {
let mut known_dimensions = Size::NONE;
let root_available_space = {
let style = tree.get_core_container_style(root);
let margin =
style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
available_space.maybe_sub(margin.sum_axes()).maybe_max(Size::ZERO)
};
#[cfg(feature = "block_layout")]
{
use crate::BoxSizing;
let parent_size = available_space.into_options();
let style = tree.get_core_container_style(root);
if style.is_block() {
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_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } 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 = 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);
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 available_space_based_size = Size { width: root_available_space.width.into_option(), height: None };
let styled_based_known_dimensions = known_dimensions
.or(min_max_definite_size)
.or(clamped_style_size)
.or(available_space_based_size)
.maybe_max(padding_border_size);
known_dimensions = styled_based_known_dimensions;
}
}
let inputs = LayoutInput {
known_dimensions,
known_dimensions_are_definite: Size { width: true, height: true },
parent_size: available_space.into_options(),
available_space: root_available_space,
sizing_mode: SizingMode::InherentSize,
axis: RequestedAxis::Both,
run_mode: RunMode::PerformLayout,
vertical_margins_are_collapsible: Line::FALSE,
};
*root_is_cached = tree.cache_get(root, &inputs).is_some();
let mut output = tree.compute_child_layout(root, inputs);
let style = tree.get_core_container_style(root);
let padding =
style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
let border =
style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
let margin =
style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
let scrollbar_size = Size {
width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
};
let is_rtl = style.direction().is_rtl();
let mut location = Point {
x: match (is_rtl, available_space.width.into_option()) {
(true, Some(available_width)) => available_width - output.size.width - margin.right,
_ => margin.left,
},
y: margin.top,
};
if style.position() == Position::Relative {
let icb_size = available_space.into_options();
let inset = style.inset();
let left = inset.left.maybe_resolve(icb_size.width, |val, basis| tree.calc(val, basis));
let right = inset.right.maybe_resolve(icb_size.width, |val, basis| tree.calc(val, basis));
let top = inset.top.maybe_resolve(icb_size.height, |val, basis| tree.calc(val, basis));
let bottom = inset.bottom.maybe_resolve(icb_size.height, |val, basis| tree.calc(val, basis));
location.x += if is_rtl {
right.map(|right| -right).or(left).unwrap_or(0.0)
} else {
left.or(right.map(|right| -right)).unwrap_or(0.0)
};
location.y += top.or(bottom.map(|bottom| -bottom)).unwrap_or(0.0);
}
drop(style);
let layout = Layout {
order: 0,
location,
size: output.size,
#[cfg(feature = "content_size")]
scrollable_overflow_rect: output.scrollable_overflow_rect,
scrollbar_size,
padding,
border,
margin,
};
tree.set_unrounded_layout(root, &layout);
(layout, output.oof_candidates.take())
}
#[inline(always)]
pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
tree: &mut Tree,
node: NodeId,
inputs: LayoutInput,
compute_uncached: ComputeFunction,
) -> LayoutOutput
where
ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
{
debug_push_node!(node);
let cache_entry = tree.cache_get(node, &inputs);
if let Some(cached_size_and_baselines) = cache_entry {
debug_log_node!(inputs);
debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
debug_pop_node!();
return cached_size_and_baselines;
}
debug_log_node!(inputs);
let computed_size_and_baselines = compute_uncached(tree, node, inputs);
tree.cache_store(node, &inputs, computed_size_and_baselines.clone());
debug_log!("RESULT", dbg:computed_size_and_baselines.size);
debug_pop_node!();
computed_size_and_baselines
}
pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
return round_layout_inner(tree, node_id, 0.0, 0.0);
fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
let unrounded_layout = tree.get_unrounded_layout(node_id);
let mut layout = unrounded_layout;
let parent_x = cumulative_x;
let parent_y = cumulative_y;
let cumulative_x = cumulative_x + unrounded_layout.location.x;
let cumulative_y = cumulative_y + unrounded_layout.location.y;
layout.location.x = round(cumulative_x) - round(parent_x);
layout.location.y = round(cumulative_y) - round(parent_y);
layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
layout.border.right = round(cumulative_x + unrounded_layout.size.width)
- round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
- round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
- round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
- round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
#[cfg(feature = "content_size")]
round_scrollable_overflow_rect(
&mut layout,
unrounded_layout.scrollable_overflow_rect,
cumulative_x,
cumulative_y,
);
tree.set_final_layout(node_id, &layout);
let child_count = tree.child_count(node_id);
for index in 0..child_count {
let child = tree.get_child_id(node_id, index);
if !tree.is_out_of_flow(child) {
round_layout_inner(tree, child, cumulative_x, cumulative_y);
}
}
let hoisted_count = tree.hoisted_child_count(node_id);
for index in 0..hoisted_count {
let child = tree.get_hoisted_child_id(node_id, index);
round_layout_inner(tree, child, cumulative_x, cumulative_y);
}
}
#[cfg(feature = "content_size")]
#[inline(always)]
fn round_scrollable_overflow_rect(
layout: &mut Layout,
unrounded_rect: crate::geometry::Rect<f32>,
cumulative_x: f32,
cumulative_y: f32,
) {
layout.scrollable_overflow_rect.left = round(cumulative_x + unrounded_rect.left) - round(cumulative_x);
layout.scrollable_overflow_rect.right = round(cumulative_x + unrounded_rect.right) - round(cumulative_x);
layout.scrollable_overflow_rect.top = round(cumulative_y + unrounded_rect.top) - round(cumulative_y);
layout.scrollable_overflow_rect.bottom = round(cumulative_y + unrounded_rect.bottom) - round(cumulative_y);
}
}
pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
tree.cache_clear(node);
tree.set_unrounded_layout(node, &Layout::with_order(0));
for index in 0..tree.child_count(node) {
let child_id = tree.get_child_id(node, index);
tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
}
LayoutOutput::HIDDEN
}
pub mod detailed_info {
#[cfg(feature = "grid")]
pub use super::grid::{
DetailedGridInfo, DetailedGridItemsInfo, DetailedGridTracksInfo, GridLineNames, GridLineNamesIter,
};
}
#[cfg(test)]
mod tests {
use super::compute_hidden_layout;
use crate::geometry::{Point, Size};
use crate::style::{Display, Style};
use crate::TaffyTree;
#[test]
fn hidden_layout_should_hide_recursively() {
let mut taffy: TaffyTree<()> = TaffyTree::new();
let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
let root = taffy
.new_with_children(
Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
&[child_00, child_01],
)
.unwrap();
compute_hidden_layout(&mut taffy.as_layout_tree(), root);
for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
let layout = taffy.layout(node).unwrap();
assert_eq!(layout.size, Size::zero());
assert_eq!(layout.location, Point::zero());
}
}
}