Remove unused abs_x/_y variables from round_layout (#319)
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}