gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1111HTML 1039Markdown 12TOML 94541 Sutichaykuna

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

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