Refactor `LayoutTree` + add examples of custom trees (#564)
1//! The layout algorithms themselves
2
3pub(crate) mod common;
4pub(crate) mod leaf;
5
6#[cfg(feature = "block_layout")]
7pub(crate) mod block;
8
9#[cfg(feature = "flexbox")]
10pub(crate) mod flexbox;
11
12#[cfg(feature = "grid")]
13pub(crate) mod grid;
14
15pub use leaf::compute_leaf_layout;
16
17#[cfg(feature = "block_layout")]
18pub use self::block::compute_block_layout;
19
20#[cfg(feature = "flexbox")]
21pub use self::flexbox::compute_flexbox_layout;
22
23#[cfg(feature = "grid")]
24pub use self::grid::compute_grid_layout;
25
26use crate::geometry::{Line, Point, Size};
27use crate::style::AvailableSpace;
28use crate::tree::{
29 Layout, LayoutInput, LayoutOutput, LayoutTree, NodeId, PartialLayoutTree, PartialLayoutTreeExt, RunMode, SizingMode,
30};
31use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
32use crate::util::sys::round;
33
34/// Updates the stored layout of the provided `node` and its children
35pub fn compute_layout(tree: &mut impl PartialLayoutTree, root: NodeId, available_space: Size<AvailableSpace>) {
36 // Recursively compute node layout
37 let size_and_baselines = tree.perform_child_layout(
38 root,
39 Size::NONE,
40 available_space.into_options(),
41 available_space,
42 SizingMode::InherentSize,
43 Line::FALSE,
44 );
45
46 let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
47 *tree.get_unrounded_layout_mut(root) = layout;
48}
49
50/// Updates the stored layout of the provided `node` and its children
51#[inline(always)]
52pub fn compute_cached_layout<Tree: PartialLayoutTree + ?Sized, ComputeFunction>(
53 tree: &mut Tree,
54 node: NodeId,
55 inputs: LayoutInput,
56 mut compute_uncached: ComputeFunction,
57) -> LayoutOutput
58where
59 ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
60{
61 debug_push_node!(node);
62
63 let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
64 let has_children = tree.child_count(node) > 0;
65
66 // First we check if we have a cached result for the given input
67 let cache_run_mode = if !has_children { RunMode::PerformLayout } else { run_mode };
68 let cache_entry = tree.get_cache_mut(node).get(known_dimensions, available_space, cache_run_mode);
69 if let Some(cached_size_and_baselines) = cache_entry {
70 debug_log!("CACHE", dbg:cached_size_and_baselines.size);
71 debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
72 debug_pop_node!();
73 return cached_size_and_baselines;
74 }
75
76 let computed_size_and_baselines = compute_uncached(tree, node, inputs);
77
78 // Cache result
79 tree.get_cache_mut(node).store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
80
81 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
82 debug_pop_node!();
83
84 computed_size_and_baselines
85}
86
87/// Rounds the calculated [`Layout`] to exact pixel values
88///
89/// In order to ensure that no gaps in the layout are introduced we:
90/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
91/// parent-relative coordinates
92/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
93/// rather than rounding the width/height directly
94/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
95///
96/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
97/// and write to `final_layout`.
98pub fn round_layout(tree: &mut impl LayoutTree, node_id: NodeId) {
99 return round_layout_inner(tree, node_id, 0.0, 0.0);
100 /// Recursive function to apply rounding to all descendents
101 fn round_layout_inner(tree: &mut impl LayoutTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
102 let unrounded_layout = *tree.get_unrounded_layout_mut(node_id);
103 let layout = &mut tree.get_final_layout_mut(node_id);
104
105 let cumulative_x = cumulative_x + unrounded_layout.location.x;
106 let cumulative_y = cumulative_y + unrounded_layout.location.y;
107
108 layout.location.x = round(unrounded_layout.location.x);
109 layout.location.y = round(unrounded_layout.location.y);
110 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
111 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
112
113 let child_count = tree.child_count(node_id);
114 for index in 0..child_count {
115 let child = tree.get_child_id(node_id, index);
116 round_layout_inner(tree, child, cumulative_x, cumulative_y);
117 }
118 }
119}
120
121/// Creates a layout for this node and its children, recursively.
122/// Each hidden node has zero size and is placed at the origin
123pub fn compute_hidden_layout(tree: &mut impl PartialLayoutTree, node: NodeId) -> LayoutOutput {
124 // Clear cache and set zeroed-out layout for the node
125 tree.get_cache_mut(node).clear();
126 *tree.get_unrounded_layout_mut(node) = Layout::with_order(0);
127
128 // Perform hidden layout on all children
129 for index in 0..tree.child_count(node) {
130 let child_id = tree.get_child_id(node, index);
131 tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
132 }
133
134 LayoutOutput::HIDDEN
135}
136
137#[cfg(test)]
138mod tests {
139 use super::compute_hidden_layout;
140 use crate::geometry::{Point, Size};
141 use crate::style::{Display, Style};
142 use crate::Taffy;
143
144 #[test]
145 fn hidden_layout_should_hide_recursively() {
146 let mut taffy: Taffy<()> = Taffy::new();
147
148 let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
149
150 let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
151 let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
152 let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
153
154 let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
155 let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
156
157 let root = taffy
158 .new_with_children(
159 Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
160 &[child_00, child_01],
161 )
162 .unwrap();
163
164 compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
165
166 // Whatever size and display-mode the nodes had previously,
167 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
168 for (node, _) in taffy.nodes.iter().filter(|(node, _)| *node != root.into()) {
169 if let Ok(layout) = taffy.layout(node.into()) {
170 assert_eq!(layout.size, Size::zero());
171 assert_eq!(layout.location, Point::zero());
172 }
173 }
174 }
175}