Make CSS Grid feature a default feature (#297)
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}