//! Computation specific for the default `Taffy` tree implementation use crate::compute::{leaf, LayoutAlgorithm}; use crate::geometry::{Line, Point, Size}; use crate::style::{AvailableSpace, Display}; use crate::tree::{ Layout, LayoutTree, NodeId, RunMode, SizeBaselinesAndMargins, SizingMode, Taffy, TaffyError, TaffyView, }; use crate::util::sys::round; #[cfg(feature = "block_layout")] use crate::compute::BlockAlgorithm; #[cfg(feature = "flexbox")] use crate::compute::FlexboxAlgorithm; #[cfg(feature = "grid")] use crate::compute::CssGridAlgorithm; #[cfg(feature = "morphorm")] use crate::compute::MorphormAlgorithm; #[cfg(any(feature = "debug", feature = "profile"))] use crate::util::debug::NODE_LOGGER; #[cfg(feature = "debug")] fn debug_log_node( known_dimensions: Size>, parent_size: Size>, available_space: Size, run_mode: RunMode, sizing_mode: SizingMode, ) { NODE_LOGGER.debug_log(run_mode); NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode); NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions); NODE_LOGGER.labelled_debug_log("parent_size", parent_size); NODE_LOGGER.labelled_debug_log("available_space", available_space); } /// Updates the stored layout of the provided `node` and its children pub(crate) fn compute_layout( taffy_view: &mut TaffyView, root: NodeId, available_space: Size, ) -> Result<(), TaffyError> where MeasureFunction: FnMut(Size>, Size, NodeId, Option<&mut NodeContext>) -> Size, { // Recursively compute node layout let size_and_baselines = perform_node_layout( taffy_view, root, Size::NONE, available_space.into_options(), available_space, SizingMode::InherentSize, Line::FALSE, ); let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO }; *taffy_view.layout_mut(root) = layout; // If rounding is enabled, recursively round the layout's of this node and all children if taffy_view.taffy.config.use_rounding { round_layout(taffy_view.taffy, root, 0.0, 0.0); } Ok(()) } /// Perform full layout on a node. Chooses which algorithm to use based on the `display` property. pub(crate) fn perform_node_layout( taffy_view: &mut TaffyView, node: NodeId, known_dimensions: Size>, parent_size: Size>, available_space: Size, sizing_mode: SizingMode, vertical_margins_are_collapsible: Line, ) -> SizeBaselinesAndMargins where MeasureFunction: FnMut(Size>, Size, NodeId, Option<&mut NodeContext>) -> Size, { compute_node_layout( taffy_view, node, known_dimensions, parent_size, available_space, RunMode::PerformLayout, sizing_mode, vertical_margins_are_collapsible, ) } /// Measure a node's size. Chooses which algorithm to use based on the `display` property. pub(crate) fn measure_node_size( taffy_view: &mut TaffyView, node: NodeId, known_dimensions: Size>, parent_size: Size>, available_space: Size, sizing_mode: SizingMode, vertical_margins_are_collapsible: Line, ) -> Size where MeasureFunction: FnMut(Size>, Size, NodeId, Option<&mut NodeContext>) -> Size, { compute_node_layout( taffy_view, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize, sizing_mode, vertical_margins_are_collapsible, ) .size } /// Updates the stored layout of the provided `node` and its children #[allow(clippy::too_many_arguments)] fn compute_node_layout( taffy_view: &mut TaffyView, node: NodeId, known_dimensions: Size>, parent_size: Size>, available_space: Size, run_mode: RunMode, sizing_mode: SizingMode, vertical_margins_are_collapsible: Line, ) -> SizeBaselinesAndMargins where MeasureFunction: FnMut(Size>, Size, NodeId, Option<&mut NodeContext>) -> Size, { #[cfg(any(feature = "debug", feature = "profile"))] NODE_LOGGER.push_node(node); #[cfg(feature = "debug")] println!(); let node_key = node.into(); let has_children = !taffy_view.taffy.children[node_key].is_empty(); // First we check if we have a cached result for the given input let cache_run_mode = if !has_children { RunMode::PerformLayout } else { run_mode }; if let Some(cached_size_and_baselines) = taffy_view.taffy.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode) { #[cfg(feature = "debug")] NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size); #[cfg(feature = "debug")] debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode); #[cfg(any(feature = "debug", feature = "profile"))] NODE_LOGGER.pop_node(); return cached_size_and_baselines; } #[cfg(feature = "debug")] debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode); /// Inlined function generic over the LayoutAlgorithm to reduce code duplication #[inline(always)] fn perform_computations( tree: &mut impl LayoutTree, node: NodeId, known_dimensions: Size>, parent_size: Size>, available_space: Size, run_mode: RunMode, sizing_mode: SizingMode, vertical_margins_are_collapsible: Line, ) -> SizeBaselinesAndMargins { #[cfg(feature = "debug")] NODE_LOGGER.log(Algorithm::NAME); match run_mode { RunMode::PerformLayout => Algorithm::perform_layout( tree, node, known_dimensions, parent_size, available_space, sizing_mode, vertical_margins_are_collapsible, ), RunMode::ComputeSize => Algorithm::measure_size( tree, node, known_dimensions, parent_size, available_space, sizing_mode, vertical_margins_are_collapsible, ) .into(), } } let display_mode = taffy_view.taffy.nodes[node_key].style.display; let computed_size_and_baselines = match (display_mode, has_children) { (Display::None, _) => { perform_taffy_tree_hidden_layout(taffy_view.taffy, node); SizeBaselinesAndMargins::HIDDEN } #[cfg(feature = "block_layout")] (Display::Block, true) => perform_computations::( taffy_view, node, known_dimensions, parent_size, available_space, run_mode, sizing_mode, vertical_margins_are_collapsible, ), #[cfg(feature = "flexbox")] (Display::Flex, true) => perform_computations::( taffy_view, node, known_dimensions, parent_size, available_space, run_mode, sizing_mode, vertical_margins_are_collapsible, ), #[cfg(feature = "grid")] (Display::Grid, true) => perform_computations::( taffy_view, node, known_dimensions, parent_size, available_space, run_mode, sizing_mode, vertical_margins_are_collapsible, ), #[cfg(feature = "morphorm")] (Display::Morphorm, true) => perform_computations::( taffy_view, node, known_dimensions, parent_size, available_space, run_mode, sizing_mode, vertical_margins_are_collapsible, ), (_, false) => match run_mode { RunMode::PerformLayout => leaf::perform_layout( &taffy_view.taffy.nodes[node_key].style, known_dimensions, parent_size, available_space, sizing_mode, &mut taffy_view.measure_function, node, taffy_view.taffy.nodes[node_key] .needs_measure .then(|| &mut taffy_view.taffy.node_context_data[node_key]), ), RunMode::ComputeSize => leaf::measure_size( &taffy_view.taffy.nodes[node_key].style, known_dimensions, parent_size, available_space, sizing_mode, &mut taffy_view.measure_function, node, taffy_view.taffy.nodes[node_key] .needs_measure .then(|| &mut taffy_view.taffy.node_context_data[node_key]), ) .into(), }, }; // Cache result taffy_view.taffy.nodes[node_key].cache.store( known_dimensions, available_space, cache_run_mode, computed_size_and_baselines, ); #[cfg(feature = "debug")] NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size); #[cfg(any(feature = "debug", feature = "profile"))] NODE_LOGGER.pop_node(); computed_size_and_baselines } /// Creates a layout for this node and its children, recursively. /// Each hidden node has zero size and is placed at the origin fn perform_taffy_tree_hidden_layout(tree: &mut Taffy, node: NodeId) { /// Recursive function to apply hidden layout to all descendents fn perform_hidden_layout_inner(tree: &mut Taffy, node: NodeId, order: u32) { let node_key = node.into(); tree.nodes[node_key].unrounded_layout = Layout::with_order(order); tree.nodes[node_key].final_layout = Layout::with_order(order); tree.nodes[node_key].cache.clear(); for order in 0..tree.children[node_key].len() { perform_hidden_layout_inner(tree, tree.children[node_key][order], order as _); } } let node_key = node.into(); for order in 0..tree.children[node.into()].len() { perform_hidden_layout_inner(tree, tree.children[node_key][order], order as _); } } /// Rounds the calculated [`Layout`] to exact pixel values /// /// In order to ensure that no gaps in the layout are introduced we: /// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than /// parent-relative coordinates /// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference /// rather than rounding the width/height directly /// See for more context /// /// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout` /// and write to `final_layout`. fn round_layout(tree: &mut Taffy, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) { let node = &mut tree.nodes[node_id.into()]; let unrounded_layout = node.unrounded_layout; let layout = &mut node.final_layout; let cumulative_x = cumulative_x + unrounded_layout.location.x; let cumulative_y = cumulative_y + unrounded_layout.location.y; layout.location.x = round(unrounded_layout.location.x); layout.location.y = round(unrounded_layout.location.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); let child_count = tree.child_count(node_id).unwrap(); for index in 0..child_count { let child = tree.children[node_id.into()][index]; round_layout(tree, child, cumulative_x, cumulative_y); } }