taffy/compute/
mod.rs

1//! Low-level access to the layout algorithms themselves. For a higher-level API, see the [`TaffyTree`](crate::TaffyTree) struct.
2//!
3//! ### Layout functions
4//!
5//! The layout functions all take an [`&mut impl LayoutPartialTree`](crate::LayoutPartialTree) parameter, which represents a single container node and it's direct children.
6//!
7//! | Function                          | Purpose                                                                                                                                                                                            |
8//! | ---                               | ---                                                                                                                                                                                                |
9//! | [`compute_flexbox_layout`]        | Layout a Flexbox container and it's direct children                                                                                                                                                |
10//! | [`compute_grid_layout`]           | Layout a CSS Grid container and it's direct children                                                                                                                                               |
11//! | [`compute_block_layout`]          | Layout a Block container and it's direct children                                                                                                                                                  |
12//! | [`compute_leaf_layout`]           | Applies common properties like padding/border/aspect-ratio to a node before deferring to a passed closure to determine it's size. Can be applied to nodes like text or image nodes.                |
13//! | [`compute_root_layout`]           | Layout the root node of a tree (regardless of it's layout mode). This function is typically called once to begin a layout run.                                                                     |                                                                      |
14//! | [`compute_hidden_layout`]         | Mark a node as hidden during layout (like `Display::None`)                                                                                                                                         |
15//! | [`compute_cached_layout`]         | Attempts to find a cached layout for the specified node and layout inputs. Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found. |
16//!
17//! ### Other functions
18//!
19//! | Function                          | Requires                                                                                                                                                                                           | Purpose                                                              |
20//! | ---                               | ---                                                                                                                                                                                                | ---                                                                  |
21//! | [`round_layout`]                  | [`RoundTree`]                                                                                                                                                                                      | Round a tree of float-valued layouts to integer pixels               |
22//! | [`print_tree`](crate::print_tree) | [`PrintTree`](crate::PrintTree)                                                                                                                                                                    | Print a debug representation of a node tree and it's computed layout |
23//!
24pub(crate) mod common;
25pub(crate) mod leaf;
26
27#[cfg(feature = "block_layout")]
28pub(crate) mod block;
29
30#[cfg(feature = "float_layout")]
31pub(crate) mod float;
32
33#[cfg(feature = "flexbox")]
34pub(crate) mod flexbox;
35
36#[cfg(feature = "grid")]
37pub(crate) mod grid;
38
39pub use leaf::compute_leaf_layout;
40
41#[cfg(feature = "block_layout")]
42pub use self::block::{compute_block_layout, BlockContext, BlockFormattingContext};
43
44#[cfg(feature = "flexbox")]
45pub use self::flexbox::compute_flexbox_layout;
46
47#[cfg(feature = "grid")]
48pub use self::grid::compute_grid_layout;
49
50#[cfg(feature = "float_layout")]
51pub use self::float::{ContentSlot, FloatContext, FloatIntrinsicWidthCalculator};
52
53use crate::geometry::{Line, Point, Size};
54use crate::style::{AvailableSpace, CoreStyle, Overflow};
55use crate::tree::{
56    Layout, LayoutInput, LayoutOutput, LayoutPartialTree, LayoutPartialTreeExt, NodeId, RoundTree, SizingMode,
57};
58use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
59use crate::util::sys::round;
60use crate::util::ResolveOrZero;
61use crate::{CacheTree, MaybeMath, MaybeResolve};
62
63/// Compute layout for the root node in the tree
64pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
65    let mut known_dimensions = Size::NONE;
66
67    #[cfg(feature = "block_layout")]
68    {
69        use crate::BoxSizing;
70
71        let parent_size = available_space.into_options();
72        let style = tree.get_core_container_style(root);
73
74        if style.is_block() {
75            // Pull these out earlier to avoid borrowing issues
76            let aspect_ratio = style.aspect_ratio();
77            let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
78            let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
79            let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
80            let padding_border_size = (padding + border).sum_axes();
81            let box_sizing_adjustment =
82                if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
83
84            let min_size = style
85                .min_size()
86                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
87                .maybe_apply_aspect_ratio(aspect_ratio)
88                .maybe_add(box_sizing_adjustment);
89            let max_size = style
90                .max_size()
91                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
92                .maybe_apply_aspect_ratio(aspect_ratio)
93                .maybe_add(box_sizing_adjustment);
94            let clamped_style_size = style
95                .size()
96                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
97                .maybe_apply_aspect_ratio(aspect_ratio)
98                .maybe_add(box_sizing_adjustment)
99                .maybe_clamp(min_size, max_size);
100
101            // If both min and max in a given axis are set and max <= min then this determines the size in that axis
102            let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
103                (Some(min), Some(max)) if max <= min => Some(min),
104                _ => None,
105            });
106
107            // Block nodes automatically stretch fit their width to fit available space if available space is definite
108            let available_space_based_size = Size {
109                width: available_space.width.into_option().maybe_sub(margin.horizontal_axis_sum()),
110                height: None,
111            };
112
113            let styled_based_known_dimensions = known_dimensions
114                .or(min_max_definite_size)
115                .or(clamped_style_size)
116                .or(available_space_based_size)
117                .maybe_max(padding_border_size);
118
119            known_dimensions = styled_based_known_dimensions;
120        }
121    }
122
123    // Recursively compute node layout
124    let output = tree.perform_child_layout(
125        root,
126        known_dimensions,
127        available_space.into_options(),
128        available_space,
129        SizingMode::InherentSize,
130        Line::FALSE,
131    );
132    let style = tree.get_core_container_style(root);
133    let padding =
134        style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
135    let border =
136        style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
137    let margin =
138        style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
139    let scrollbar_size = Size {
140        width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
141        height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
142    };
143    let location = Point {
144        x: if style.direction().is_rtl() {
145            available_space.width.into_option().map_or(0.0, |available_width| available_width - output.size.width)
146        } else {
147            0.0
148        },
149        y: 0.0,
150    };
151    drop(style);
152
153    tree.set_unrounded_layout(
154        root,
155        &Layout {
156            order: 0,
157            location,
158            size: output.size,
159            #[cfg(feature = "content_size")]
160            content_size: output.content_size,
161            scrollbar_size,
162            padding,
163            border,
164            // TODO: support auto margins for root node?
165            margin,
166        },
167    );
168}
169
170/// Attempts to find a cached layout for the specified node and layout inputs.
171///
172/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
173#[inline(always)]
174pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
175    tree: &mut Tree,
176    node: NodeId,
177    inputs: LayoutInput,
178    compute_uncached: ComputeFunction,
179) -> LayoutOutput
180where
181    ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
182{
183    debug_push_node!(node);
184
185    // First we check if we have a cached result for the given input
186    let cache_entry = tree.cache_get(node, &inputs);
187    if let Some(cached_size_and_baselines) = cache_entry {
188        debug_log_node!(inputs);
189        debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
190        debug_pop_node!();
191        return cached_size_and_baselines;
192    }
193
194    debug_log_node!(inputs);
195
196    let computed_size_and_baselines = compute_uncached(tree, node, inputs);
197
198    // Cache result
199    tree.cache_store(node, &inputs, computed_size_and_baselines);
200
201    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
202    debug_pop_node!();
203
204    computed_size_and_baselines
205}
206
207/// Rounds the calculated layout to exact pixel values
208///
209/// In order to ensure that no gaps in the layout are introduced we:
210///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
211///     parent-relative coordinates
212///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
213///     rather than rounding the width/height directly
214///
215/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
216///
217/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
218/// and write to `final_layout`.
219pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
220    return round_layout_inner(tree, node_id, 0.0, 0.0);
221
222    /// Recursive function to apply rounding to all descendents
223    fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
224        let unrounded_layout = tree.get_unrounded_layout(node_id);
225        let mut layout = unrounded_layout;
226
227        let cumulative_x = cumulative_x + unrounded_layout.location.x;
228        let cumulative_y = cumulative_y + unrounded_layout.location.y;
229
230        layout.location.x = round(unrounded_layout.location.x);
231        layout.location.y = round(unrounded_layout.location.y);
232        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
233        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
234        layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
235        layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
236        layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
237        layout.border.right = round(cumulative_x + unrounded_layout.size.width)
238            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
239        layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
240        layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
241            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
242        layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
243        layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
244            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
245        layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
246        layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
247            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
248
249        #[cfg(feature = "content_size")]
250        round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
251
252        tree.set_final_layout(node_id, &layout);
253
254        let child_count = tree.child_count(node_id);
255        for index in 0..child_count {
256            let child = tree.get_child_id(node_id, index);
257            round_layout_inner(tree, child, cumulative_x, cumulative_y);
258        }
259    }
260
261    #[cfg(feature = "content_size")]
262    #[inline(always)]
263    /// Round content size variables.
264    /// This is split into a separate function to make it easier to feature flag.
265    fn round_content_size(
266        layout: &mut Layout,
267        unrounded_content_size: Size<f32>,
268        cumulative_x: f32,
269        cumulative_y: f32,
270    ) {
271        layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
272        layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
273    }
274}
275
276/// Creates a layout for this node and its children, recursively.
277/// Each hidden node has zero size and is placed at the origin
278pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
279    // Clear cache and set zeroed-out layout for the node
280    tree.cache_clear(node);
281    tree.set_unrounded_layout(node, &Layout::with_order(0));
282
283    // Perform hidden layout on all children
284    for index in 0..tree.child_count(node) {
285        let child_id = tree.get_child_id(node, index);
286        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
287    }
288
289    LayoutOutput::HIDDEN
290}
291
292/// A module for unified re-exports of detailed layout info structs, used by low level API
293#[cfg(feature = "detailed_layout_info")]
294pub mod detailed_info {
295    #[cfg(feature = "grid")]
296    pub use super::grid::{DetailedGridInfo, DetailedGridItemsInfo, DetailedGridTracksInfo};
297}
298
299#[cfg(test)]
300mod tests {
301    use super::compute_hidden_layout;
302    use crate::geometry::{Point, Size};
303    use crate::style::{Display, Style};
304    use crate::TaffyTree;
305
306    #[test]
307    fn hidden_layout_should_hide_recursively() {
308        let mut taffy: TaffyTree<()> = TaffyTree::new();
309
310        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
311
312        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
313        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
314        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
315
316        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
317        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
318
319        let root = taffy
320            .new_with_children(
321                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
322                &[child_00, child_01],
323            )
324            .unwrap();
325
326        compute_hidden_layout(&mut taffy.as_layout_tree(), root);
327
328        // Whatever size and display-mode the nodes had previously,
329        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
330
331        for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
332            let layout = taffy.layout(node).unwrap();
333            assert_eq!(layout.size, Size::zero());
334            assert_eq!(layout.location, Point::zero());
335        }
336    }
337}