tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

⎇ 3bda6444237f92c60a8a9ba24495554b9faa72c3 src/compute/mod.rs Rust · 340 siq'ikuna · 11.7 KB Ñawpa willay Pi imata llamkarqan ch'uya

Move cache logic to own module (#432) 3bda64442 · Nico Burns ·

1//! The layout algorithms themselves
2
3pub(crate) mod common;
4pub(crate) mod leaf;
5
6#[cfg(feature = "flexbox")]
7pub(crate) mod flexbox;
8
9#[cfg(feature = "grid")]
10pub(crate) mod grid;
11
12use crate::error::TaffyError;
13use crate::geometry::{Point, Size};
14use crate::layout::{Layout, RunMode, SizeAndBaselines, SizingMode};
15use crate::node::Taffy;
16use crate::style::{AvailableSpace, Display};
17use crate::sys::round;
18use crate::tree::{LayoutTree, NodeId};
19
20#[cfg(feature = "flexbox")]
21use self::flexbox::FlexboxAlgorithm;
22
23#[cfg(feature = "grid")]
24use self::grid::CssGridAlgorithm;
25
26#[cfg(any(feature = "debug", feature = "profile"))]
27use crate::debug::NODE_LOGGER;
28
29/// Updates the stored layout of the provided `node` and its children
30pub fn compute_layout(
31    taffy: &mut Taffy,
32    root: NodeId,
33    available_space: Size<AvailableSpace>,
34) -> Result<(), TaffyError> {
35    // Recursively compute node layout
36    let size_and_baselines = perform_node_layout(
37        taffy,
38        root,
39        Size::NONE,
40        available_space.into_options(),
41        available_space,
42        SizingMode::InherentSize,
43    );
44
45    let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
46    *taffy.layout_mut(root) = layout;
47
48    // If rounding is enabled, recursively round the layout's of this node and all children
49    if taffy.config.use_rounding {
50        round_layout(taffy, root, 0.0, 0.0);
51    }
52
53    Ok(())
54}
55
56/// A common interface that all Taffy layout algorithms conform to
57pub(crate) trait LayoutAlgorithm {
58    /// The name of the algorithm (mainly used for debug purposes)
59    const NAME: &'static str;
60
61    /// Compute the size of the node given the specified constraints
62    fn measure_size(
63        tree: &mut impl LayoutTree,
64        node: NodeId,
65        known_dimensions: Size<Option<f32>>,
66        parent_size: Size<Option<f32>>,
67        available_space: Size<AvailableSpace>,
68        sizing_mode: SizingMode,
69    ) -> Size<f32>;
70
71    /// Perform a full layout on the node given the specified constraints
72    fn perform_layout(
73        tree: &mut impl LayoutTree,
74        node: NodeId,
75        known_dimensions: Size<Option<f32>>,
76        parent_size: Size<Option<f32>>,
77        available_space: Size<AvailableSpace>,
78        sizing_mode: SizingMode,
79    ) -> SizeAndBaselines;
80}
81
82/// Perform full layout on a node. Chooses which algorithm to use based on the `display` property.
83pub(crate) fn perform_node_layout(
84    tree: &mut Taffy,
85    node: NodeId,
86    known_dimensions: Size<Option<f32>>,
87    parent_size: Size<Option<f32>>,
88    available_space: Size<AvailableSpace>,
89    sizing_mode: SizingMode,
90) -> SizeAndBaselines {
91    compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::PeformLayout, sizing_mode)
92}
93
94/// Measure a node's size. Chooses which algorithm to use based on the `display` property.
95pub(crate) fn measure_node_size(
96    tree: &mut Taffy,
97    node: NodeId,
98    known_dimensions: Size<Option<f32>>,
99    parent_size: Size<Option<f32>>,
100    available_space: Size<AvailableSpace>,
101    sizing_mode: SizingMode,
102) -> Size<f32> {
103    compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize, sizing_mode)
104        .size
105}
106
107/// Updates the stored layout of the provided `node` and its children
108fn compute_node_layout(
109    tree: &mut Taffy,
110    node: NodeId,
111    known_dimensions: Size<Option<f32>>,
112    parent_size: Size<Option<f32>>,
113    available_space: Size<AvailableSpace>,
114    run_mode: RunMode,
115    sizing_mode: SizingMode,
116) -> SizeAndBaselines {
117    #[cfg(any(feature = "debug", feature = "profile"))]
118    NODE_LOGGER.push_node(node);
119    #[cfg(feature = "debug")]
120    println!();
121
122    let node_key = node.into();
123    let has_children = !tree.children[node_key].is_empty();
124
125    // First we check if we have a cached result for the given input
126    let cache_run_mode = if !has_children { RunMode::PeformLayout } else { run_mode };
127    if let Some(cached_size_and_baselines) =
128        tree.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode)
129    {
130        #[cfg(feature = "debug")]
131        NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
132        #[cfg(feature = "debug")]
133        debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
134        #[cfg(any(feature = "debug", feature = "profile"))]
135        NODE_LOGGER.pop_node();
136        return cached_size_and_baselines;
137    }
138
139    #[cfg(feature = "debug")]
140    debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
141
142    /// Inlined function generic over the LayoutAlgorithm to reduce code duplication
143    #[inline(always)]
144    fn perform_computations<Algorithm: LayoutAlgorithm>(
145        tree: &mut impl LayoutTree,
146        node: NodeId,
147        known_dimensions: Size<Option<f32>>,
148        parent_size: Size<Option<f32>>,
149        available_space: Size<AvailableSpace>,
150        run_mode: RunMode,
151        sizing_mode: SizingMode,
152    ) -> SizeAndBaselines {
153        #[cfg(feature = "debug")]
154        NODE_LOGGER.log(Algorithm::NAME);
155
156        match run_mode {
157            RunMode::PeformLayout => {
158                Algorithm::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
159            }
160            RunMode::ComputeSize => {
161                Algorithm::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
162            }
163        }
164    }
165
166    let display_mode = tree.nodes[node_key].style.display;
167    let computed_size_and_baselines = match (display_mode, has_children) {
168        (Display::None, _) => perform_computations::<HiddenAlgorithm>(
169            tree,
170            node,
171            known_dimensions,
172            parent_size,
173            available_space,
174            run_mode,
175            sizing_mode,
176        ),
177        #[cfg(feature = "flexbox")]
178        (Display::Flex, true) => perform_computations::<FlexboxAlgorithm>(
179            tree,
180            node,
181            known_dimensions,
182            parent_size,
183            available_space,
184            run_mode,
185            sizing_mode,
186        ),
187        #[cfg(feature = "grid")]
188        (Display::Grid, true) => perform_computations::<CssGridAlgorithm>(
189            tree,
190            node,
191            known_dimensions,
192            parent_size,
193            available_space,
194            run_mode,
195            sizing_mode,
196        ),
197        (_, false) => match run_mode {
198            RunMode::PeformLayout => {
199                leaf::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
200            }
201            RunMode::ComputeSize => {
202                leaf::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
203            }
204        },
205    };
206
207    // Cache result
208    tree.nodes[node_key].cache.store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
209
210    #[cfg(feature = "debug")]
211    NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
212    #[cfg(any(feature = "debug", feature = "profile"))]
213    NODE_LOGGER.pop_node();
214
215    computed_size_and_baselines
216}
217
218#[cfg(feature = "debug")]
219fn debug_log_node(
220    known_dimensions: Size<Option<f32>>,
221    parent_size: Size<Option<f32>>,
222    available_space: Size<AvailableSpace>,
223    run_mode: RunMode,
224    sizing_mode: SizingMode,
225) {
226    NODE_LOGGER.debug_log(run_mode);
227    NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
228    NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
229    NODE_LOGGER.labelled_debug_log("parent_size", parent_size);
230    NODE_LOGGER.labelled_debug_log("available_space", available_space);
231}
232
233/// The public interface to Taffy's hidden node algorithm implementation
234struct HiddenAlgorithm;
235impl LayoutAlgorithm for HiddenAlgorithm {
236    const NAME: &'static str = "NONE";
237
238    fn perform_layout(
239        tree: &mut impl LayoutTree,
240        node: NodeId,
241        _known_dimensions: Size<Option<f32>>,
242        _parent_size: Size<Option<f32>>,
243        _available_space: Size<AvailableSpace>,
244        _sizing_mode: SizingMode,
245    ) -> SizeAndBaselines {
246        perform_hidden_layout(tree, node);
247        SizeAndBaselines { size: Size::ZERO, first_baselines: Point::NONE }
248    }
249
250    fn measure_size(
251        _tree: &mut impl LayoutTree,
252        _node: NodeId,
253        _known_dimensions: Size<Option<f32>>,
254        _parent_size: Size<Option<f32>>,
255        _available_space: Size<AvailableSpace>,
256        _sizing_mode: SizingMode,
257    ) -> Size<f32> {
258        Size::ZERO
259    }
260}
261
262/// Creates a layout for this node and its children, recursively.
263/// Each hidden node has zero size and is placed at the origin
264fn perform_hidden_layout(tree: &mut impl LayoutTree, node: NodeId) {
265    /// Recursive function to apply hidden layout to all descendents
266    fn perform_hidden_layout_inner(tree: &mut impl LayoutTree, node: NodeId, order: u32) {
267        *tree.layout_mut(node) = Layout::with_order(order);
268        for order in 0..tree.child_count(node) {
269            perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
270        }
271    }
272
273    for order in 0..tree.child_count(node) {
274        perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
275    }
276}
277
278/// Rounds the calculated [`Layout`] to exact pixel values
279/// In order to ensure that no gaps in the layout are introduced we:
280///   - Always round based on the absolute coordinates rather than parent-relative coordinates
281///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
282///     rather than rounding the width/height directly
283///
284/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
285fn round_layout(tree: &mut impl LayoutTree, node: NodeId, abs_x: f32, abs_y: f32) {
286    let layout = tree.layout_mut(node);
287    let abs_x = abs_x + layout.location.x;
288    let abs_y = abs_y + layout.location.y;
289
290    layout.location.x = round(layout.location.x);
291    layout.location.y = round(layout.location.y);
292    layout.size.width = round(abs_x + layout.size.width) - round(abs_x);
293    layout.size.height = round(abs_y + layout.size.height) - round(abs_y);
294
295    let child_count = tree.child_count(node);
296    for index in 0..child_count {
297        let child = tree.child(node, index);
298        round_layout(tree, child, abs_x, abs_y);
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::perform_hidden_layout;
305    use crate::geometry::{Point, Size};
306    use crate::style::{Display, Style};
307    use crate::Taffy;
308
309    #[test]
310    fn hidden_layout_should_hide_recursively() {
311        let mut taffy = Taffy::new();
312
313        let style: Style = Style { display: Display::Flex, size: Size::from_points(50.0, 50.0), ..Default::default() };
314
315        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
316        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
317        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
318
319        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
320        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
321
322        let root = taffy
323            .new_with_children(
324                Style { display: Display::None, size: Size::from_points(50.0, 50.0), ..Default::default() },
325                &[child_00, child_01],
326            )
327            .unwrap();
328
329        perform_hidden_layout(&mut taffy, root.into());
330
331        // Whatever size and display-mode the nodes had previously,
332        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
333        for (node, _) in taffy.nodes.iter().filter(|(node, _)| *node != root.into()) {
334            if let Ok(layout) = taffy.layout(node.into()) {
335                assert_eq!(layout.size, Size::zero());
336                assert_eq!(layout.location, Point::zero());
337            }
338        }
339    }
340}