gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 86Markdown 12TOML 91841 Definitions

⎇ d4374b93f0ec2c0ace42d3feafec22d3af1940d8 src/compute/mod.rs Rust · 175 lines · 6.7 KB History Who touched what raw

Refactor `LayoutTree` + add examples of custom trees (#564) d4374b93f · Nico Burns ·

1//! The layout algorithms themselves
2
3pub(crate) mod common;
4pub(crate) mod leaf;
5
6#[cfg(feature = "block_layout")]
7pub(crate) mod block;
8
9#[cfg(feature = "flexbox")]
10pub(crate) mod flexbox;
11
12#[cfg(feature = "grid")]
13pub(crate) mod grid;
14
15pub use leaf::compute_leaf_layout;
16
17#[cfg(feature = "block_layout")]
18pub use self::block::compute_block_layout;
19
20#[cfg(feature = "flexbox")]
21pub use self::flexbox::compute_flexbox_layout;
22
23#[cfg(feature = "grid")]
24pub use self::grid::compute_grid_layout;
25
26use crate::geometry::{Line, Point, Size};
27use crate::style::AvailableSpace;
28use crate::tree::{
29    Layout, LayoutInput, LayoutOutput, LayoutTree, NodeId, PartialLayoutTree, PartialLayoutTreeExt, RunMode, SizingMode,
30};
31use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
32use crate::util::sys::round;
33
34/// Updates the stored layout of the provided `node` and its children
35pub fn compute_layout(tree: &mut impl PartialLayoutTree, root: NodeId, available_space: Size<AvailableSpace>) {
36    // Recursively compute node layout
37    let size_and_baselines = tree.perform_child_layout(
38        root,
39        Size::NONE,
40        available_space.into_options(),
41        available_space,
42        SizingMode::InherentSize,
43        Line::FALSE,
44    );
45
46    let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
47    *tree.get_unrounded_layout_mut(root) = layout;
48}
49
50/// Updates the stored layout of the provided `node` and its children
51#[inline(always)]
52pub fn compute_cached_layout<Tree: PartialLayoutTree + ?Sized, ComputeFunction>(
53    tree: &mut Tree,
54    node: NodeId,
55    inputs: LayoutInput,
56    mut compute_uncached: ComputeFunction,
57) -> LayoutOutput
58where
59    ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
60{
61    debug_push_node!(node);
62
63    let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
64    let has_children = tree.child_count(node) > 0;
65
66    // First we check if we have a cached result for the given input
67    let cache_run_mode = if !has_children { RunMode::PerformLayout } else { run_mode };
68    let cache_entry = tree.get_cache_mut(node).get(known_dimensions, available_space, cache_run_mode);
69    if let Some(cached_size_and_baselines) = cache_entry {
70        debug_log!("CACHE", dbg:cached_size_and_baselines.size);
71        debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
72        debug_pop_node!();
73        return cached_size_and_baselines;
74    }
75
76    let computed_size_and_baselines = compute_uncached(tree, node, inputs);
77
78    // Cache result
79    tree.get_cache_mut(node).store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
80
81    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
82    debug_pop_node!();
83
84    computed_size_and_baselines
85}
86
87/// Rounds the calculated [`Layout`] to exact pixel values
88///
89/// In order to ensure that no gaps in the layout are introduced we:
90///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
91///     parent-relative coordinates
92///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
93///     rather than rounding the width/height directly
94/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
95///
96/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
97/// and write to `final_layout`.
98pub fn round_layout(tree: &mut impl LayoutTree, node_id: NodeId) {
99    return round_layout_inner(tree, node_id, 0.0, 0.0);
100    /// Recursive function to apply rounding to all descendents
101    fn round_layout_inner(tree: &mut impl LayoutTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
102        let unrounded_layout = *tree.get_unrounded_layout_mut(node_id);
103        let layout = &mut tree.get_final_layout_mut(node_id);
104
105        let cumulative_x = cumulative_x + unrounded_layout.location.x;
106        let cumulative_y = cumulative_y + unrounded_layout.location.y;
107
108        layout.location.x = round(unrounded_layout.location.x);
109        layout.location.y = round(unrounded_layout.location.y);
110        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
111        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
112
113        let child_count = tree.child_count(node_id);
114        for index in 0..child_count {
115            let child = tree.get_child_id(node_id, index);
116            round_layout_inner(tree, child, cumulative_x, cumulative_y);
117        }
118    }
119}
120
121/// Creates a layout for this node and its children, recursively.
122/// Each hidden node has zero size and is placed at the origin
123pub fn compute_hidden_layout(tree: &mut impl PartialLayoutTree, node: NodeId) -> LayoutOutput {
124    // Clear cache and set zeroed-out layout for the node
125    tree.get_cache_mut(node).clear();
126    *tree.get_unrounded_layout_mut(node) = Layout::with_order(0);
127
128    // Perform hidden layout on all children
129    for index in 0..tree.child_count(node) {
130        let child_id = tree.get_child_id(node, index);
131        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
132    }
133
134    LayoutOutput::HIDDEN
135}
136
137#[cfg(test)]
138mod tests {
139    use super::compute_hidden_layout;
140    use crate::geometry::{Point, Size};
141    use crate::style::{Display, Style};
142    use crate::Taffy;
143
144    #[test]
145    fn hidden_layout_should_hide_recursively() {
146        let mut taffy: Taffy<()> = Taffy::new();
147
148        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
149
150        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
151        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
152        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
153
154        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
155        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
156
157        let root = taffy
158            .new_with_children(
159                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
160                &[child_00, child_01],
161            )
162            .unwrap();
163
164        compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
165
166        // Whatever size and display-mode the nodes had previously,
167        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
168        for (node, _) in taffy.nodes.iter().filter(|(node, _)| *node != root.into()) {
169            if let Ok(layout) = taffy.layout(node.into()) {
170                assert_eq!(layout.size, Size::zero());
171                assert_eq!(layout.location, Point::zero());
172            }
173        }
174    }
175}