gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 86Markdown 12TOML 91841 Sutichaykuna

⎇ 6ce86a9e53b741a8b58e0daa5d4bdfe80ec1f456 src/compute/mod.rs Rust · 337 siq'ikuna · 11.7 KB Ñawpa willay Pi imata llamkarqan ch'uya

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