tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 499HTML 245JavaScript 8Markdown 71218 Definitions

⎇ 16823d17d0953ce30d0d4d733dc33c830b29a647 src/compute/mod.rs Rust · 249 lines · 13.0 KB History Who touched what raw

Document low-level traits (#604) 16823d17d · 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, parent_size, available_space, run_mode, 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/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
135///
136/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
137/// and write to `final_layout`.
138pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
139    return round_layout_inner(tree, node_id, 0.0, 0.0);
140
141    /// Recursive function to apply rounding to all descendents
142    fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
143        let unrounded_layout = *tree.get_unrounded_layout(node_id);
144        let mut layout = unrounded_layout;
145
146        let cumulative_x = cumulative_x + unrounded_layout.location.x;
147        let cumulative_y = cumulative_y + unrounded_layout.location.y;
148
149        layout.location.x = round(unrounded_layout.location.x);
150        layout.location.y = round(unrounded_layout.location.y);
151        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
152        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
153        layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
154        layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
155        layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
156        layout.border.right = round(cumulative_x + unrounded_layout.size.width)
157            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
158        layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
159        layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
160            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
161        layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
162        layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
163            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
164        layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
165        layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
166            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
167
168        #[cfg(feature = "content_size")]
169        round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
170
171        tree.set_final_layout(node_id, &layout);
172
173        let child_count = tree.child_count(node_id);
174        for index in 0..child_count {
175            let child = tree.get_child_id(node_id, index);
176            round_layout_inner(tree, child, cumulative_x, cumulative_y);
177        }
178    }
179
180    #[cfg(feature = "content_size")]
181    #[inline(always)]
182    /// Round content size variables.
183    /// This is split into a separate function to make it easier to feature flag.
184    fn round_content_size(
185        layout: &mut Layout,
186        unrounded_content_size: Size<f32>,
187        cumulative_x: f32,
188        cumulative_y: f32,
189    ) {
190        layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
191        layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
192    }
193}
194
195/// Creates a layout for this node and its children, recursively.
196/// Each hidden node has zero size and is placed at the origin
197pub fn compute_hidden_layout(tree: &mut impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
198    // Clear cache and set zeroed-out layout for the node
199    tree.get_cache_mut(node).clear();
200    tree.set_unrounded_layout(node, &Layout::with_order(0));
201
202    // Perform hidden layout on all children
203    for index in 0..tree.child_count(node) {
204        let child_id = tree.get_child_id(node, index);
205        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
206    }
207
208    LayoutOutput::HIDDEN
209}
210
211#[cfg(test)]
212mod tests {
213    use super::compute_hidden_layout;
214    use crate::geometry::{Point, Size};
215    use crate::style::{Display, Style};
216    use crate::TaffyTree;
217
218    #[test]
219    fn hidden_layout_should_hide_recursively() {
220        let mut taffy: TaffyTree<()> = TaffyTree::new();
221
222        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
223
224        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
225        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
226        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
227
228        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
229        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
230
231        let root = taffy
232            .new_with_children(
233                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
234                &[child_00, child_01],
235            )
236            .unwrap();
237
238        compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
239
240        // Whatever size and display-mode the nodes had previously,
241        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
242
243        for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
244            let layout = taffy.layout(node.into()).unwrap();
245            assert_eq!(layout.size, Size::zero());
246            assert_eq!(layout.location, Point::zero());
247        }
248    }
249}