taffy/compute/
mod.rs

1//! The layout algorithms themselves
2
3pub(crate) mod common;
4pub(crate) mod leaf;
5
6#[cfg(feature = "block_layout")]
7pub(crate) mod block;
8
9#[cfg(feature = "flexbox")]
10pub(crate) mod flexbox;
11
12#[cfg(feature = "grid")]
13pub(crate) mod grid;
14
15pub use leaf::compute_leaf_layout;
16
17#[cfg(feature = "block_layout")]
18pub use self::block::compute_block_layout;
19
20#[cfg(feature = "flexbox")]
21pub use self::flexbox::compute_flexbox_layout;
22
23#[cfg(feature = "grid")]
24pub use self::grid::compute_grid_layout;
25
26use crate::geometry::{Line, Point, Size};
27use crate::style::{AvailableSpace, Overflow};
28use crate::tree::{
29    Layout, LayoutInput, LayoutOutput, LayoutTree, NodeId, PartialLayoutTree, PartialLayoutTreeExt, SizingMode,
30};
31use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
32use crate::util::sys::round;
33use crate::util::ResolveOrZero;
34
35/// Updates the stored layout of the provided `node` and its children
36pub fn compute_layout(tree: &mut impl PartialLayoutTree, root: NodeId, available_space: Size<AvailableSpace>) {
37    // Recursively compute node layout
38    let output = tree.perform_child_layout(
39        root,
40        Size::NONE,
41        available_space.into_options(),
42        available_space,
43        SizingMode::InherentSize,
44        Line::FALSE,
45    );
46
47    let style = tree.get_style(root);
48    let padding = style.padding.resolve_or_zero(available_space.width.into_option());
49    let border = style.border.resolve_or_zero(available_space.width.into_option());
50    let scrollbar_size = Size {
51        width: if style.overflow.y == Overflow::Scroll { style.scrollbar_width } else { 0.0 },
52        height: if style.overflow.x == Overflow::Scroll { style.scrollbar_width } else { 0.0 },
53    };
54
55    tree.set_unrounded_layout(
56        root,
57        &Layout {
58            order: 0,
59            location: Point::ZERO,
60            size: output.size,
61            #[cfg(feature = "content_size")]
62            content_size: output.content_size,
63            scrollbar_size,
64            padding,
65            border,
66        },
67    );
68}
69
70/// Updates the stored layout of the provided `node` and its children
71#[inline(always)]
72pub fn compute_cached_layout<Tree: PartialLayoutTree + ?Sized, ComputeFunction>(
73    tree: &mut Tree,
74    node: NodeId,
75    inputs: LayoutInput,
76    mut compute_uncached: ComputeFunction,
77) -> LayoutOutput
78where
79    ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
80{
81    debug_push_node!(node);
82    let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
83
84    // First we check if we have a cached result for the given input
85    let cache_entry = tree.get_cache_mut(node).get(known_dimensions, available_space, run_mode);
86    if let Some(cached_size_and_baselines) = cache_entry {
87        debug_log!("CACHE", dbg:cached_size_and_baselines.size);
88        debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
89        debug_pop_node!();
90        return cached_size_and_baselines;
91    }
92
93    let computed_size_and_baselines = compute_uncached(tree, node, inputs);
94
95    // Cache result
96    tree.get_cache_mut(node).store(known_dimensions, available_space, run_mode, computed_size_and_baselines);
97
98    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
99    debug_pop_node!();
100
101    computed_size_and_baselines
102}
103
104/// Rounds the calculated [`Layout`] to exact pixel values
105///
106/// In order to ensure that no gaps in the layout are introduced we:
107///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
108///     parent-relative coordinates
109///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
110///     rather than rounding the width/height directly
111/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
112///
113/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
114/// and write to `final_layout`.
115pub fn round_layout(tree: &mut impl LayoutTree, node_id: NodeId) {
116    return round_layout_inner(tree, node_id, 0.0, 0.0);
117
118    /// Recursive function to apply rounding to all descendents
119    fn round_layout_inner(tree: &mut impl LayoutTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
120        let unrounded_layout = *tree.get_unrounded_layout(node_id);
121        let mut layout = unrounded_layout;
122
123        let cumulative_x = cumulative_x + unrounded_layout.location.x;
124        let cumulative_y = cumulative_y + unrounded_layout.location.y;
125
126        layout.location.x = round(unrounded_layout.location.x);
127        layout.location.y = round(unrounded_layout.location.y);
128        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
129        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
130        layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
131        layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
132        layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
133        layout.border.right = round(cumulative_x + unrounded_layout.size.width)
134            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
135        layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
136        layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
137            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
138        layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
139        layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
140            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
141        layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
142        layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
143            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
144
145        #[cfg(feature = "content_size")]
146        round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
147
148        tree.set_final_layout(node_id, &layout);
149
150        let child_count = tree.child_count(node_id);
151        for index in 0..child_count {
152            let child = tree.get_child_id(node_id, index);
153            round_layout_inner(tree, child, cumulative_x, cumulative_y);
154        }
155    }
156
157    #[cfg(feature = "content_size")]
158    #[inline(always)]
159    /// Round content size variables.
160    /// This is split into a separate function to make it easier to feature flag.
161    fn round_content_size(
162        layout: &mut Layout,
163        unrounded_content_size: Size<f32>,
164        cumulative_x: f32,
165        cumulative_y: f32,
166    ) {
167        layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
168        layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
169    }
170}
171
172/// Creates a layout for this node and its children, recursively.
173/// Each hidden node has zero size and is placed at the origin
174pub fn compute_hidden_layout(tree: &mut impl PartialLayoutTree, node: NodeId) -> LayoutOutput {
175    // Clear cache and set zeroed-out layout for the node
176    tree.get_cache_mut(node).clear();
177    tree.set_unrounded_layout(node, &Layout::with_order(0));
178
179    // Perform hidden layout on all children
180    for index in 0..tree.child_count(node) {
181        let child_id = tree.get_child_id(node, index);
182        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
183    }
184
185    LayoutOutput::HIDDEN
186}
187
188#[cfg(test)]
189mod tests {
190    use super::compute_hidden_layout;
191    use crate::geometry::{Point, Size};
192    use crate::style::{Display, Style};
193    use crate::TaffyTree;
194
195    #[test]
196    fn hidden_layout_should_hide_recursively() {
197        let mut taffy: TaffyTree<()> = TaffyTree::new();
198
199        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
200
201        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
202        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
203        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
204
205        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
206        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
207
208        let root = taffy
209            .new_with_children(
210                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
211                &[child_00, child_01],
212            )
213            .unwrap();
214
215        compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
216
217        // Whatever size and display-mode the nodes had previously,
218        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
219        for (node, _) in taffy.nodes.iter().filter(|(node, _)| *node != root.into()) {
220            if let Ok(layout) = taffy.layout(node.into()) {
221                assert_eq!(layout.size, Size::zero());
222                assert_eq!(layout.location, Point::zero());
223            }
224        }
225    }
226}