gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 707HTML 660Markdown 12TOML 62344 Definitions

⎇ 320118ed7bae2764c9fc634ed0827927113abd05 src/compute/mod.rs Rust · 288 lines · 11.4 KB History Who touched what raw

Remove unused abs_x/_y variables from round_layout (#319) 320118ed7 · Benjamin Woodruff ·

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