tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Sutichaykuna

⎇ cd73db35cfa2b31555a796e7972cd97a45554e17 src/compute/mod.rs Rust · 252 siq'ikuna · 13.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

Fix computation of intrinsic main size when it depends on a child's known cross size (#673) cd73db35c · 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_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
111        debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
112        debug_pop_node!();
113        return cached_size_and_baselines;
114    }
115
116    debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
117
118    let computed_size_and_baselines = compute_uncached(tree, node, inputs);
119
120    // Cache result
121    tree.get_cache_mut(node).store(known_dimensions, available_space, run_mode, computed_size_and_baselines);
122
123    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
124    debug_pop_node!();
125
126    computed_size_and_baselines
127}
128
129/// Rounds the calculated layout to exact pixel values
130///
131/// In order to ensure that no gaps in the layout are introduced we:
132///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
133///     parent-relative coordinates
134///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
135///     rather than rounding the width/height directly
136///
137/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
138///
139/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
140/// and write to `final_layout`.
141pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
142    return round_layout_inner(tree, node_id, 0.0, 0.0);
143
144    /// Recursive function to apply rounding to all descendents
145    fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
146        let unrounded_layout = *tree.get_unrounded_layout(node_id);
147        let mut layout = unrounded_layout;
148
149        let cumulative_x = cumulative_x + unrounded_layout.location.x;
150        let cumulative_y = cumulative_y + unrounded_layout.location.y;
151
152        layout.location.x = round(unrounded_layout.location.x);
153        layout.location.y = round(unrounded_layout.location.y);
154        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
155        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
156        layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
157        layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
158        layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
159        layout.border.right = round(cumulative_x + unrounded_layout.size.width)
160            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
161        layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
162        layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
163            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
164        layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
165        layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
166            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
167        layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
168        layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
169            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
170
171        #[cfg(feature = "content_size")]
172        round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
173
174        tree.set_final_layout(node_id, &layout);
175
176        let child_count = tree.child_count(node_id);
177        for index in 0..child_count {
178            let child = tree.get_child_id(node_id, index);
179            round_layout_inner(tree, child, cumulative_x, cumulative_y);
180        }
181    }
182
183    #[cfg(feature = "content_size")]
184    #[inline(always)]
185    /// Round content size variables.
186    /// This is split into a separate function to make it easier to feature flag.
187    fn round_content_size(
188        layout: &mut Layout,
189        unrounded_content_size: Size<f32>,
190        cumulative_x: f32,
191        cumulative_y: f32,
192    ) {
193        layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
194        layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
195    }
196}
197
198/// Creates a layout for this node and its children, recursively.
199/// Each hidden node has zero size and is placed at the origin
200pub fn compute_hidden_layout(tree: &mut impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
201    // Clear cache and set zeroed-out layout for the node
202    tree.get_cache_mut(node).clear();
203    tree.set_unrounded_layout(node, &Layout::with_order(0));
204
205    // Perform hidden layout on all children
206    for index in 0..tree.child_count(node) {
207        let child_id = tree.get_child_id(node, index);
208        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
209    }
210
211    LayoutOutput::HIDDEN
212}
213
214#[cfg(test)]
215mod tests {
216    use super::compute_hidden_layout;
217    use crate::geometry::{Point, Size};
218    use crate::style::{Display, Style};
219    use crate::TaffyTree;
220
221    #[test]
222    fn hidden_layout_should_hide_recursively() {
223        let mut taffy: TaffyTree<()> = TaffyTree::new();
224
225        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
226
227        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
228        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
229        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
230
231        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
232        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
233
234        let root = taffy
235            .new_with_children(
236                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
237                &[child_00, child_01],
238            )
239            .unwrap();
240
241        compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
242
243        // Whatever size and display-mode the nodes had previously,
244        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
245
246        for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
247            let layout = taffy.layout(node.into()).unwrap();
248            assert_eq!(layout.size, Size::zero());
249            assert_eq!(layout.location, Point::zero());
250        }
251    }
252}