gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 86Markdown 12TOML 91841 Definiciones

⎇ 01babab32d8138f3c3fa05095fb6c65fca0d3967 src/compute/mod.rs Rust · 308 líneas · 12.8 KB Historia Quién tocó qué crudo

Make CSS Grid feature a default feature (#297) 01babab32 · Nico Burns ·

1//! The layout algorithms themselves
2
3pub(crate) mod common;
4pub(crate) mod flexbox;
5pub(crate) mod leaf;
6
7#[cfg(feature = "grid")]
8pub(crate) mod grid;
9
10use crate::data::CACHE_SIZE;
11use crate::error::TaffyError;
12use crate::geometry::{Point, Size};
13use crate::layout::{Cache, Layout, RunMode, SizingMode};
14use crate::node::Node;
15use crate::style::{AvailableSpace, Display};
16use crate::sys::round;
17use crate::tree::LayoutTree;
18
19#[cfg(feature = "debug")]
20use crate::debug::NODE_LOGGER;
21
22/// Updates the stored layout of the provided `node` and its children
23pub fn compute_layout(
24    tree: &mut impl LayoutTree,
25    root: Node,
26    available_space: Size<AvailableSpace>,
27) -> Result<(), TaffyError> {
28    // Recursively compute node layout
29    let size =
30        compute_node_layout(tree, root, Size::NONE, available_space, RunMode::PeformLayout, SizingMode::InherentSize);
31
32    let layout = Layout { order: 0, size, location: Point::ZERO };
33    *tree.layout_mut(root) = layout;
34
35    // Recursively round the layout's of this node and all children
36    round_layout(tree, root, 0.0, 0.0);
37
38    Ok(())
39}
40
41/// Updates the stored layout of the provided `node` and its children
42fn compute_node_layout(
43    tree: &mut impl LayoutTree,
44    node: Node,
45    known_dimensions: Size<Option<f32>>,
46    available_space: Size<AvailableSpace>,
47    run_mode: RunMode,
48    sizing_mode: SizingMode,
49) -> Size<f32> {
50    #[cfg(feature = "debug")]
51    NODE_LOGGER.push_node(node);
52    #[cfg(feature = "debug")]
53    println!();
54
55    // First we check if we have a cached result for the given input
56    let cache_run_mode = if tree.is_childless(node) { RunMode::PeformLayout } else { run_mode };
57    if let Some(cached_size) =
58        compute_from_cache(tree, node, known_dimensions, available_space, cache_run_mode, sizing_mode)
59    {
60        #[cfg(feature = "debug")]
61        NODE_LOGGER.labelled_debug_log("CACHE", cached_size);
62        #[cfg(feature = "debug")]
63        NODE_LOGGER.labelled_debug_log("run_mode", run_mode);
64        #[cfg(feature = "debug")]
65        NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
66        #[cfg(feature = "debug")]
67        NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
68        #[cfg(feature = "debug")]
69        NODE_LOGGER.labelled_debug_log("available_space", available_space);
70        #[cfg(feature = "debug")]
71        NODE_LOGGER.pop_node();
72        return cached_size;
73    }
74
75    #[cfg(feature = "debug")]
76    NODE_LOGGER.log("COMPUTE");
77    #[cfg(feature = "debug")]
78    NODE_LOGGER.labelled_debug_log("run_mode", run_mode);
79    #[cfg(feature = "debug")]
80    NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
81    #[cfg(feature = "debug")]
82    NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
83    #[cfg(feature = "debug")]
84    NODE_LOGGER.labelled_debug_log("available_space", available_space);
85
86    // Attempt to shortcut size computation based on
87    //  - KnownSize sizing constraints
88    //  - The node's preferred sizes (width/heights) styles and AvailableSpace sizing constraints
89    // (percentages resolve to pixel values if there is a definite AvailableSpace sizing constraint)
90    // let style = tree.style(node);
91    // // let known_node_size = available_space.
92    // //     .zip_map(style.size, |constraint, style_size| style_size.maybe_resolve(constraint.as_option()));
93    // let known_node_size = style
94    //     .size
95    //     .maybe_resolve(available_space.as_options())
96    //     .zip_map(known_dimensions, |style_size, known_dimensions| known_dimensions.or(style_size));
97    // if run_mode == RunMode::ComputeSize && known_node_size.width.is_some() && known_node_size.height.is_some() {
98    //     let node_min_size = style.min_size.maybe_resolve(available_space.as_options());
99    //     let node_max_size = style.max_size.maybe_resolve(available_space.as_options());
100    //     return Size {
101    //         width: known_node_size.width.maybe_max(node_min_size.width).maybe_min(node_max_size.width).unwrap_or(0.0),
102    //         height: known_node_size
103    //             .height
104    //             .maybe_max(node_min_size.height)
105    //             .maybe_min(node_max_size.height)
106    //             .unwrap_or(0.0),
107    //     };
108    // }
109
110    // If this is a leaf node we can skip a lot of this function in some cases
111    let computed_size = if tree.is_childless(node) {
112        #[cfg(feature = "debug")]
113        NODE_LOGGER.log("Algo: leaf");
114        self::leaf::compute(tree, node, known_dimensions, available_space, run_mode, sizing_mode)
115    } else {
116        // println!("match {:?}", tree.style(node).display);
117        match tree.style(node).display {
118            Display::Flex => {
119                #[cfg(feature = "debug")]
120                NODE_LOGGER.log("Algo: flexbox");
121                self::flexbox::compute(tree, node, known_dimensions, available_space, run_mode)
122            }
123            #[cfg(feature = "grid")]
124            Display::Grid => self::grid::compute(tree, node, available_space),
125            Display::None => {
126                #[cfg(feature = "debug")]
127                NODE_LOGGER.log("Algo: none");
128                perform_hidden_layout(tree, node)
129            }
130        }
131    };
132
133    // Cache result
134    let cache_slot = compute_cache_slot(known_dimensions, available_space);
135    *tree.cache_mut(node, cache_slot) =
136        Some(Cache { known_dimensions, available_space, run_mode: cache_run_mode, cached_size: computed_size });
137
138    #[cfg(feature = "debug")]
139    NODE_LOGGER.labelled_debug_log("RESULT", computed_size);
140    #[cfg(feature = "debug")]
141    NODE_LOGGER.pop_node();
142
143    computed_size
144}
145
146/// Return the cache slot to cache the current computed result in
147///
148/// ## Caching Strategy
149///
150/// We need multiple cache slots, because a node's size is often queried by it's parent multiple times in the course of the layout
151/// process, and we don't want later results to clobber earlier ones.
152///
153/// The two variables that we care about when determining cache slot are:
154///
155///   - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimensions known known_dimensions,
156///     then with one dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions
157///     known.
158///   - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
159///     shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
160///
161/// ## Cache slots:
162///
163/// - Slot 0: Both known_dimensions were set
164/// - Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
165/// - Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
166/// - Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
167/// - Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
168#[inline]
169fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
170    let has_known_width = known_dimensions.width.is_some();
171    let has_known_height = known_dimensions.height.is_some();
172
173    // Slot 0: Both known_dimensions were set
174    if has_known_width && has_known_height {
175        return 0;
176    }
177
178    // Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
179    // Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
180    if has_known_width || has_known_height {
181        let other_dim_available_space = if has_known_width { available_space.height } else { available_space.width };
182        return 1 + (other_dim_available_space == AvailableSpace::MinContent) as usize;
183    }
184
185    // Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
186    // Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
187    3 + (available_space.width == AvailableSpace::MinContent) as usize
188}
189
190/// Try to get the computation result from the cache.
191#[inline]
192fn compute_from_cache(
193    tree: &mut impl LayoutTree,
194    node: Node,
195    known_dimensions: Size<Option<f32>>,
196    available_space: Size<AvailableSpace>,
197    run_mode: RunMode,
198    sizing_mode: SizingMode,
199) -> Option<Size<f32>> {
200    for idx in 0..CACHE_SIZE {
201        let entry = tree.cache_mut(node, idx);
202        #[cfg(feature = "debug")]
203        NODE_LOGGER.labelled_debug_log("cache_entry", &entry);
204        if let Some(entry) = entry {
205            // Cached ComputeSize results are not valid if we are running in PerformLayout mode
206            if entry.run_mode == RunMode::ComputeSize && run_mode == RunMode::PeformLayout {
207                return None;
208            }
209
210            if (known_dimensions.width == entry.known_dimensions.width
211                || known_dimensions.width == Some(entry.cached_size.width))
212                && (known_dimensions.height == entry.known_dimensions.height
213                    || known_dimensions.height == Some(entry.cached_size.height))
214                && (known_dimensions.width.is_some()
215                    || entry.available_space.width.is_roughly_equal(available_space.width)
216                    || (sizing_mode == SizingMode::ContentSize
217                        && available_space.width.is_definite()
218                        && available_space.width.unwrap() >= entry.cached_size.width))
219                && (known_dimensions.height.is_some()
220                    || entry.available_space.height.is_roughly_equal(available_space.height)
221                    || (sizing_mode == SizingMode::ContentSize
222                        && available_space.height.is_definite()
223                        && available_space.height.unwrap() >= entry.cached_size.height))
224            {
225                return Some(entry.cached_size);
226            }
227        }
228    }
229
230    None
231}
232
233/// Creates a layout for this node and its children, recursively.
234/// Each hidden node has zero size and is placed at the origin
235fn perform_hidden_layout(tree: &mut impl LayoutTree, node: Node) -> Size<f32> {
236    /// Recursive function to apply hidden layout to all descendents
237    fn perform_hidden_layout_inner(tree: &mut impl LayoutTree, node: Node, order: u32) {
238        *tree.layout_mut(node) = Layout::with_order(order);
239        for order in 0..tree.child_count(node) {
240            perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
241        }
242    }
243
244    for order in 0..tree.child_count(node) {
245        perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
246    }
247
248    Size::ZERO
249}
250
251/// Rounds the calculated [`NodeData`] according to the spec
252fn round_layout(tree: &mut impl LayoutTree, root: Node, abs_x: f32, abs_y: f32) {
253    let layout = tree.layout_mut(root);
254    let abs_x = abs_x + layout.location.x;
255    let abs_y = abs_y + layout.location.y;
256
257    layout.location.x = round(layout.location.x);
258    layout.location.y = round(layout.location.y);
259
260    layout.size.width = round(layout.size.width);
261    layout.size.height = round(layout.size.height);
262
263    // Satisfy the borrow checker here by re-indexing to shorten the lifetime to the loop scope
264    for x in 0..tree.child_count(root) {
265        let child = tree.child(root, x);
266        round_layout(tree, child, abs_x, abs_y);
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::perform_hidden_layout;
273    use crate::geometry::{Point, Size};
274    use crate::style::{Display, Style};
275    use crate::Taffy;
276
277    #[test]
278    fn hidden_layout_should_hide_recursively() {
279        let mut taffy = Taffy::new();
280
281        let style: Style = Style { display: Display::Flex, size: Size::from_points(50.0, 50.0), ..Default::default() };
282
283        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
284        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
285        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
286
287        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
288        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
289
290        let root = taffy
291            .new_with_children(
292                Style { display: Display::None, size: Size::from_points(50.0, 50.0), ..Default::default() },
293                &[child_00, child_01],
294            )
295            .unwrap();
296
297        perform_hidden_layout(&mut taffy, root);
298
299        // Whatever size and display-mode the nodes had previously,
300        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
301        for (node, _) in taffy.nodes.iter().filter(|(node, _)| *node != root) {
302            if let Ok(layout) = taffy.layout(node) {
303                assert_eq!(layout.size, Size::zero());
304                assert_eq!(layout.location, Point::zero());
305            }
306        }
307    }
308}