gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 497HTML 254C 19JavaScript 81329 Definitions

⎇ ceed1d7b81edb7d35eb6ce61f502f3a6b5d70a29 src/compute/mod.rs Rust · 323 lines · 16.4 KB History Who touched what raw

Doc: ensure detailed grid info types are public / documented (#822) ceed1d7b8 · Nico Burns ·

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