gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 496HTML 245JavaScript 8CSS 61214 Sutichaykuna

⎇ 9e53f03adea2b770d68954e404d2fa407dfc7d40 src/compute/mod.rs Rust · 206 siq'ikuna · 8.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

Layout algorithm decoupling, Sizing Constraints & Perf Improvements (#246) ba4cd3c3b · Nico Burns ·

1//! The layout algorithms themselves
2
3pub(crate) mod flexbox;
4pub(crate) mod leaf;
5
6use crate::error::TaffyError;
7use crate::geometry::{Point, Size};
8use crate::layout::{AvailableSpace, Cache, Layout, RunMode, SizingMode};
9use crate::node::Node;
10use crate::style::Display;
11use crate::sys::round;
12use crate::tree::LayoutTree;
13
14#[cfg(feature = "debug")]
15use crate::debug::NODE_LOGGER;
16
17/// Updates the stored layout of the provided `node` and its children
18pub fn compute_layout(
19    tree: &mut impl LayoutTree,
20    root: Node,
21    available_space: Size<AvailableSpace>,
22) -> Result<(), TaffyError> {
23    // Recursively compute node layout
24    let size =
25        compute_node_layout(tree, root, Size::NONE, available_space, RunMode::PeformLayout, SizingMode::InherentSize);
26
27    let layout = Layout { order: 0, size, location: Point::ZERO };
28    *tree.layout_mut(root) = layout;
29
30    // Recursively round the layout's of this node and all children
31    round_layout(tree, root, 0.0, 0.0);
32
33    Ok(())
34}
35
36/// Updates the stored layout of the provided `node` and its children
37fn compute_node_layout(
38    tree: &mut impl LayoutTree,
39    node: Node,
40    known_dimensions: Size<Option<f32>>,
41    available_space: Size<AvailableSpace>,
42    run_mode: RunMode,
43    sizing_mode: SizingMode,
44) -> Size<f32> {
45    // clear the dirtiness of the node now that we've computed it
46    tree.mark_dirty(node, false);
47
48    #[cfg(feature = "debug")]
49    NODE_LOGGER.push_node(node);
50    #[cfg(feature = "debug")]
51    println!();
52
53    // First we check if we have a cached result for the given input
54    let cache_run_mode = if tree.children(node).is_empty() { RunMode::PeformLayout } else { run_mode };
55    if let Some(cached_size) =
56        compute_from_cache(tree, node, known_dimensions, available_space, cache_run_mode, sizing_mode)
57    {
58        #[cfg(feature = "debug")]
59        NODE_LOGGER.labelled_debug_log("CACHE", cached_size);
60        #[cfg(feature = "debug")]
61        NODE_LOGGER.labelled_debug_log("run_mode", run_mode);
62        #[cfg(feature = "debug")]
63        NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
64        #[cfg(feature = "debug")]
65        NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
66        #[cfg(feature = "debug")]
67        NODE_LOGGER.labelled_debug_log("available_space", available_space);
68        #[cfg(feature = "debug")]
69        NODE_LOGGER.pop_node();
70        return cached_size;
71    }
72
73    #[cfg(feature = "debug")]
74    NODE_LOGGER.log("COMPUTE");
75    #[cfg(feature = "debug")]
76    NODE_LOGGER.labelled_debug_log("run_mode", run_mode);
77    #[cfg(feature = "debug")]
78    NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
79    #[cfg(feature = "debug")]
80    NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
81    #[cfg(feature = "debug")]
82    NODE_LOGGER.labelled_debug_log("available_space", available_space);
83
84    // Attempt to shortcut size computation based on
85    //  - KnownSize sizing constraints
86    //  - The node's preferred sizes (width/heights) styles and AvailableSpace sizing constraints
87    // (percentages resolve to pixel values if there is a definite AvailableSpace sizing constraint)
88    // let style = tree.style(node);
89    // // let known_node_size = available_space.
90    // //     .zip_map(style.size, |constraint, style_size| style_size.maybe_resolve(constraint.as_option()));
91    // let known_node_size = style
92    //     .size
93    //     .maybe_resolve(available_space.as_options())
94    //     .zip_map(known_dimensions, |style_size, known_dimensions| known_dimensions.or(style_size));
95    // if run_mode == RunMode::ComputeSize && known_node_size.width.is_some() && known_node_size.height.is_some() {
96    //     let node_min_size = style.min_size.maybe_resolve(available_space.as_options());
97    //     let node_max_size = style.max_size.maybe_resolve(available_space.as_options());
98    //     return Size {
99    //         width: known_node_size.width.maybe_max(node_min_size.width).maybe_min(node_max_size.width).unwrap_or(0.0),
100    //         height: known_node_size
101    //             .height
102    //             .maybe_max(node_min_size.height)
103    //             .maybe_min(node_max_size.height)
104    //             .unwrap_or(0.0),
105    //     };
106    // }
107
108    // If this is a leaf node we can skip a lot of this function in some cases
109    let computed_size = if tree.children(node).is_empty() {
110        #[cfg(feature = "debug")]
111        NODE_LOGGER.log("Algo: leaf");
112        self::leaf::compute(tree, node, known_dimensions, available_space, run_mode, sizing_mode)
113    } else {
114        // println!("match {:?}", tree.style(node).display);
115        match tree.style(node).display {
116            Display::Flex => {
117                #[cfg(feature = "debug")]
118                NODE_LOGGER.log("Algo: flexbox");
119                self::flexbox::compute(tree, node, known_dimensions, available_space, run_mode)
120            }
121            Display::None => {
122                #[cfg(feature = "debug")]
123                NODE_LOGGER.log("Algo: none");
124                Size { width: 0.0, height: 0.0 }
125            }
126        }
127    };
128
129    // Cache result
130    let cache_slot = (known_dimensions.width.is_some() as usize) + (known_dimensions.height.is_some() as usize * 2);
131    *tree.cache_mut(node, cache_slot) =
132        Some(Cache { known_dimensions, available_space, run_mode: cache_run_mode, cached_size: computed_size });
133
134    #[cfg(feature = "debug")]
135    NODE_LOGGER.labelled_debug_log("RESULT", computed_size);
136    #[cfg(feature = "debug")]
137    NODE_LOGGER.pop_node();
138
139    computed_size
140}
141
142/// Try to get the computation result from the cache.
143#[inline]
144fn compute_from_cache(
145    tree: &mut impl LayoutTree,
146    node: Node,
147    known_dimensions: Size<Option<f32>>,
148    available_space: Size<AvailableSpace>,
149    run_mode: RunMode,
150    sizing_mode: SizingMode,
151) -> Option<Size<f32>> {
152    for idx in 0..4 {
153        let entry = tree.cache_mut(node, idx);
154        #[cfg(feature = "debug")]
155        NODE_LOGGER.labelled_debug_log("cache_entry", &entry);
156        if let Some(entry) = entry {
157            // Cached ComputeSize results are not valid if we are running in PerformLayout mode
158            if entry.run_mode == RunMode::ComputeSize && run_mode == RunMode::PeformLayout {
159                return None;
160            }
161
162            // if known_dimensions.width == entry.known_dimensions.width
163            // && known_dimensions.height == entry.known_dimensions.height
164            if (known_dimensions.width == entry.known_dimensions.width || known_dimensions.width == Some(entry.cached_size.width))
165                && (known_dimensions.height == entry.known_dimensions.height || known_dimensions.height == Some(entry.cached_size.height))
166                // && entry.available_space.width.is_roughly_equal(available_space.width)
167                // && entry.available_space.height.is_roughly_equal(available_space.height)
168                && (
169                  known_dimensions.width.is_some()
170                  || entry.available_space.width.is_roughly_equal(available_space.width)
171                  || (sizing_mode == SizingMode::ContentSize && available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width)
172                )
173                && (
174                  known_dimensions.height.is_some()
175                  || entry.available_space.height.is_roughly_equal(available_space.height)
176                  || (sizing_mode == SizingMode::ContentSize && available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height)
177                )
178            // && (entry.available_space.width.is_roughly_equal(available_space.width) || (available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width))
179            // && (entry.available_space.height.is_roughly_equal(available_space.height) || (available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height))
180            {
181                return Some(entry.cached_size);
182            }
183        }
184    }
185
186    None
187}
188
189/// Rounds the calculated [`NodeData`] according to the spec
190fn round_layout(tree: &mut impl LayoutTree, root: Node, abs_x: f32, abs_y: f32) {
191    let layout = tree.layout_mut(root);
192    let abs_x = abs_x + layout.location.x;
193    let abs_y = abs_y + layout.location.y;
194
195    layout.location.x = round(layout.location.x);
196    layout.location.y = round(layout.location.y);
197
198    layout.size.width = round(layout.size.width);
199    layout.size.height = round(layout.size.height);
200
201    // Satisfy the borrow checker here by re-indexing to shorten the lifetime to the loop scope
202    for x in 0..tree.children(root).len() {
203        let child = tree.child(root, x);
204        round_layout(tree, child, abs_x, abs_y);
205    }
206}