gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 684HTML 640Markdown 12TOML 62286 Definitions

⎇ 09c7a718b2eaee87aa9d0505d5a98bd1d33a32db src/compute/mod.rs Rust · 250 lines · 13.0 KB History Who touched what raw

Enable correct image measure functions (#661) 09c7a718b · 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, 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;
55
56/// Compute layout for the root node in the tree
57pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
58    // Recursively compute node layout
59    let output = tree.perform_child_layout(
60        root,
61        Size::NONE,
62        available_space.into_options(),
63        available_space,
64        SizingMode::InherentSize,
65        Line::FALSE,
66    );
67
68    let style = tree.get_style(root);
69    let padding = style.padding.resolve_or_zero(available_space.width.into_option());
70    let border = style.border.resolve_or_zero(available_space.width.into_option());
71    let scrollbar_size = Size {
72        width: if style.overflow.y == Overflow::Scroll { style.scrollbar_width } else { 0.0 },
73        height: if style.overflow.x == Overflow::Scroll { style.scrollbar_width } else { 0.0 },
74    };
75
76    tree.set_unrounded_layout(
77        root,
78        &Layout {
79            order: 0,
80            location: Point::ZERO,
81            size: output.size,
82            #[cfg(feature = "content_size")]
83            content_size: output.content_size,
84            scrollbar_size,
85            padding,
86            border,
87        },
88    );
89}
90
91/// Attempts to find a cached layout for the specified node and layout inputs.
92///
93/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
94#[inline(always)]
95pub fn compute_cached_layout<Tree: LayoutPartialTree + ?Sized, ComputeFunction>(
96    tree: &mut Tree,
97    node: NodeId,
98    inputs: LayoutInput,
99    mut compute_uncached: ComputeFunction,
100) -> LayoutOutput
101where
102    ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
103{
104    debug_push_node!(node);
105    let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
106
107    // First we check if we have a cached result for the given input
108    let cache_entry = tree.get_cache_mut(node).get(known_dimensions, available_space, run_mode);
109    if let Some(cached_size_and_baselines) = cache_entry {
110        debug_log!("CACHE", dbg:cached_size_and_baselines.size);
111        debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
112        debug_pop_node!();
113        return cached_size_and_baselines;
114    }
115
116    let computed_size_and_baselines = compute_uncached(tree, node, inputs);
117
118    // Cache result
119    tree.get_cache_mut(node).store(known_dimensions, available_space, run_mode, computed_size_and_baselines);
120
121    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
122    debug_pop_node!();
123
124    computed_size_and_baselines
125}
126
127/// Rounds the calculated layout to exact pixel values
128///
129/// In order to ensure that no gaps in the layout are introduced we:
130///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
131///     parent-relative coordinates
132///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
133///     rather than rounding the width/height directly
134///
135/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
136///
137/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
138/// and write to `final_layout`.
139pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
140    return round_layout_inner(tree, node_id, 0.0, 0.0);
141
142    /// Recursive function to apply rounding to all descendents
143    fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
144        let unrounded_layout = *tree.get_unrounded_layout(node_id);
145        let mut layout = unrounded_layout;
146
147        let cumulative_x = cumulative_x + unrounded_layout.location.x;
148        let cumulative_y = cumulative_y + unrounded_layout.location.y;
149
150        layout.location.x = round(unrounded_layout.location.x);
151        layout.location.y = round(unrounded_layout.location.y);
152        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
153        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
154        layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
155        layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
156        layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
157        layout.border.right = round(cumulative_x + unrounded_layout.size.width)
158            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
159        layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
160        layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
161            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
162        layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
163        layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
164            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
165        layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
166        layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
167            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
168
169        #[cfg(feature = "content_size")]
170        round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
171
172        tree.set_final_layout(node_id, &layout);
173
174        let child_count = tree.child_count(node_id);
175        for index in 0..child_count {
176            let child = tree.get_child_id(node_id, index);
177            round_layout_inner(tree, child, cumulative_x, cumulative_y);
178        }
179    }
180
181    #[cfg(feature = "content_size")]
182    #[inline(always)]
183    /// Round content size variables.
184    /// This is split into a separate function to make it easier to feature flag.
185    fn round_content_size(
186        layout: &mut Layout,
187        unrounded_content_size: Size<f32>,
188        cumulative_x: f32,
189        cumulative_y: f32,
190    ) {
191        layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
192        layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
193    }
194}
195
196/// Creates a layout for this node and its children, recursively.
197/// Each hidden node has zero size and is placed at the origin
198pub fn compute_hidden_layout(tree: &mut impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
199    // Clear cache and set zeroed-out layout for the node
200    tree.get_cache_mut(node).clear();
201    tree.set_unrounded_layout(node, &Layout::with_order(0));
202
203    // Perform hidden layout on all children
204    for index in 0..tree.child_count(node) {
205        let child_id = tree.get_child_id(node, index);
206        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
207    }
208
209    LayoutOutput::HIDDEN
210}
211
212#[cfg(test)]
213mod tests {
214    use super::compute_hidden_layout;
215    use crate::geometry::{Point, Size};
216    use crate::style::{Display, Style};
217    use crate::TaffyTree;
218
219    #[test]
220    fn hidden_layout_should_hide_recursively() {
221        let mut taffy: TaffyTree<()> = TaffyTree::new();
222
223        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
224
225        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
226        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
227        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
228
229        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
230        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
231
232        let root = taffy
233            .new_with_children(
234                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
235                &[child_00, child_01],
236            )
237            .unwrap();
238
239        compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
240
241        // Whatever size and display-mode the nodes had previously,
242        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
243
244        for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
245            let layout = taffy.layout(node.into()).unwrap();
246            assert_eq!(layout.size, Size::zero());
247            assert_eq!(layout.location, Point::zero());
248        }
249    }
250}