⎇ 6ce86a9e53b741a8b58e0daa5d4bdfe80ec1f456
src/compute/mod.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Refactor/module hierarchy (#443)
1//! The layout algorithms themselves
2
3pub(crate) mod common;
4pub(crate) mod leaf;
5
6#[cfg(feature = "flexbox")]
7pub(crate) mod flexbox;
8
9#[cfg(feature = "grid")]
10pub(crate) mod grid;
11
12use crate::geometry::{Point, Size};
13use crate::style::{AvailableSpace, Display};
14use crate::tree::{Layout, LayoutTree, NodeId, RunMode, SizeAndBaselines, SizingMode, Taffy, TaffyError};
15use crate::util::sys::round;
16
17#[cfg(feature = "flexbox")]
18pub use self::flexbox::FlexboxAlgorithm;
19
20#[cfg(feature = "grid")]
21pub use self::grid::CssGridAlgorithm;
22
23#[cfg(any(feature = "debug", feature = "profile"))]
24use crate::util::debug::NODE_LOGGER;
25
26/// Updates the stored layout of the provided `node` and its children
27pub(crate) fn compute_layout(
28 taffy: &mut Taffy,
29 root: NodeId,
30 available_space: Size<AvailableSpace>,
31) -> Result<(), TaffyError> {
32 // Recursively compute node layout
33 let size_and_baselines = perform_node_layout(
34 taffy,
35 root,
36 Size::NONE,
37 available_space.into_options(),
38 available_space,
39 SizingMode::InherentSize,
40 );
41
42 let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
43 *taffy.layout_mut(root) = layout;
44
45 // If rounding is enabled, recursively round the layout's of this node and all children
46 if taffy.config.use_rounding {
47 round_layout(taffy, root, 0.0, 0.0);
48 }
49
50 Ok(())
51}
52
53/// A common interface that all Taffy layout algorithms conform to
54pub trait LayoutAlgorithm {
55 /// The name of the algorithm (mainly used for debug purposes)
56 const NAME: &'static str;
57
58 /// Compute the size of the node given the specified constraints
59 fn measure_size(
60 tree: &mut impl LayoutTree,
61 node: NodeId,
62 known_dimensions: Size<Option<f32>>,
63 parent_size: Size<Option<f32>>,
64 available_space: Size<AvailableSpace>,
65 sizing_mode: SizingMode,
66 ) -> Size<f32>;
67
68 /// Perform a full layout on the node given the specified constraints
69 fn perform_layout(
70 tree: &mut impl LayoutTree,
71 node: NodeId,
72 known_dimensions: Size<Option<f32>>,
73 parent_size: Size<Option<f32>>,
74 available_space: Size<AvailableSpace>,
75 sizing_mode: SizingMode,
76 ) -> SizeAndBaselines;
77}
78
79/// Perform full layout on a node. Chooses which algorithm to use based on the `display` property.
80pub(crate) fn perform_node_layout(
81 tree: &mut Taffy,
82 node: NodeId,
83 known_dimensions: Size<Option<f32>>,
84 parent_size: Size<Option<f32>>,
85 available_space: Size<AvailableSpace>,
86 sizing_mode: SizingMode,
87) -> SizeAndBaselines {
88 compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::PeformLayout, sizing_mode)
89}
90
91/// Measure a node's size. Chooses which algorithm to use based on the `display` property.
92pub(crate) fn measure_node_size(
93 tree: &mut Taffy,
94 node: NodeId,
95 known_dimensions: Size<Option<f32>>,
96 parent_size: Size<Option<f32>>,
97 available_space: Size<AvailableSpace>,
98 sizing_mode: SizingMode,
99) -> Size<f32> {
100 compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize, sizing_mode)
101 .size
102}
103
104/// Updates the stored layout of the provided `node` and its children
105fn compute_node_layout(
106 tree: &mut Taffy,
107 node: NodeId,
108 known_dimensions: Size<Option<f32>>,
109 parent_size: Size<Option<f32>>,
110 available_space: Size<AvailableSpace>,
111 run_mode: RunMode,
112 sizing_mode: SizingMode,
113) -> SizeAndBaselines {
114 #[cfg(any(feature = "debug", feature = "profile"))]
115 NODE_LOGGER.push_node(node);
116 #[cfg(feature = "debug")]
117 println!();
118
119 let node_key = node.into();
120 let has_children = !tree.children[node_key].is_empty();
121
122 // First we check if we have a cached result for the given input
123 let cache_run_mode = if !has_children { RunMode::PeformLayout } else { run_mode };
124 if let Some(cached_size_and_baselines) =
125 tree.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode)
126 {
127 #[cfg(feature = "debug")]
128 NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
129 #[cfg(feature = "debug")]
130 debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
131 #[cfg(any(feature = "debug", feature = "profile"))]
132 NODE_LOGGER.pop_node();
133 return cached_size_and_baselines;
134 }
135
136 #[cfg(feature = "debug")]
137 debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
138
139 /// Inlined function generic over the LayoutAlgorithm to reduce code duplication
140 #[inline(always)]
141 fn perform_computations<Algorithm: LayoutAlgorithm>(
142 tree: &mut impl LayoutTree,
143 node: NodeId,
144 known_dimensions: Size<Option<f32>>,
145 parent_size: Size<Option<f32>>,
146 available_space: Size<AvailableSpace>,
147 run_mode: RunMode,
148 sizing_mode: SizingMode,
149 ) -> SizeAndBaselines {
150 #[cfg(feature = "debug")]
151 NODE_LOGGER.log(Algorithm::NAME);
152
153 match run_mode {
154 RunMode::PeformLayout => {
155 Algorithm::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
156 }
157 RunMode::ComputeSize => {
158 Algorithm::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
159 }
160 }
161 }
162
163 let display_mode = tree.nodes[node_key].style.display;
164 let computed_size_and_baselines = match (display_mode, has_children) {
165 (Display::None, _) => perform_computations::<HiddenAlgorithm>(
166 tree,
167 node,
168 known_dimensions,
169 parent_size,
170 available_space,
171 run_mode,
172 sizing_mode,
173 ),
174 #[cfg(feature = "flexbox")]
175 (Display::Flex, true) => perform_computations::<FlexboxAlgorithm>(
176 tree,
177 node,
178 known_dimensions,
179 parent_size,
180 available_space,
181 run_mode,
182 sizing_mode,
183 ),
184 #[cfg(feature = "grid")]
185 (Display::Grid, true) => perform_computations::<CssGridAlgorithm>(
186 tree,
187 node,
188 known_dimensions,
189 parent_size,
190 available_space,
191 run_mode,
192 sizing_mode,
193 ),
194 (_, false) => match run_mode {
195 RunMode::PeformLayout => {
196 leaf::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
197 }
198 RunMode::ComputeSize => {
199 leaf::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
200 }
201 },
202 };
203
204 // Cache result
205 tree.nodes[node_key].cache.store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
206
207 #[cfg(feature = "debug")]
208 NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
209 #[cfg(any(feature = "debug", feature = "profile"))]
210 NODE_LOGGER.pop_node();
211
212 computed_size_and_baselines
213}
214
215#[cfg(feature = "debug")]
216fn debug_log_node(
217 known_dimensions: Size<Option<f32>>,
218 parent_size: Size<Option<f32>>,
219 available_space: Size<AvailableSpace>,
220 run_mode: RunMode,
221 sizing_mode: SizingMode,
222) {
223 NODE_LOGGER.debug_log(run_mode);
224 NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
225 NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
226 NODE_LOGGER.labelled_debug_log("parent_size", parent_size);
227 NODE_LOGGER.labelled_debug_log("available_space", available_space);
228}
229
230/// The public interface to Taffy's hidden node algorithm implementation
231struct HiddenAlgorithm;
232impl LayoutAlgorithm for HiddenAlgorithm {
233 const NAME: &'static str = "NONE";
234
235 fn perform_layout(
236 tree: &mut impl LayoutTree,
237 node: NodeId,
238 _known_dimensions: Size<Option<f32>>,
239 _parent_size: Size<Option<f32>>,
240 _available_space: Size<AvailableSpace>,
241 _sizing_mode: SizingMode,
242 ) -> SizeAndBaselines {
243 perform_hidden_layout(tree, node);
244 SizeAndBaselines { size: Size::ZERO, first_baselines: Point::NONE }
245 }
246
247 fn measure_size(
248 _tree: &mut impl LayoutTree,
249 _node: NodeId,
250 _known_dimensions: Size<Option<f32>>,
251 _parent_size: Size<Option<f32>>,
252 _available_space: Size<AvailableSpace>,
253 _sizing_mode: SizingMode,
254 ) -> Size<f32> {
255 Size::ZERO
256 }
257}
258
259/// Creates a layout for this node and its children, recursively.
260/// Each hidden node has zero size and is placed at the origin
261fn perform_hidden_layout(tree: &mut impl LayoutTree, node: NodeId) {
262 /// Recursive function to apply hidden layout to all descendents
263 fn perform_hidden_layout_inner(tree: &mut impl LayoutTree, node: NodeId, order: u32) {
264 *tree.layout_mut(node) = Layout::with_order(order);
265 for order in 0..tree.child_count(node) {
266 perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
267 }
268 }
269
270 for order in 0..tree.child_count(node) {
271 perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
272 }
273}
274
275/// Rounds the calculated [`Layout`] to exact pixel values
276/// In order to ensure that no gaps in the layout are introduced we:
277/// - Always round based on the absolute coordinates rather than parent-relative coordinates
278/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
279/// rather than rounding the width/height directly
280///
281/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
282fn round_layout(tree: &mut impl LayoutTree, node: NodeId, abs_x: f32, abs_y: f32) {
283 let layout = tree.layout_mut(node);
284 let abs_x = abs_x + layout.location.x;
285 let abs_y = abs_y + layout.location.y;
286
287 layout.location.x = round(layout.location.x);
288 layout.location.y = round(layout.location.y);
289 layout.size.width = round(abs_x + layout.size.width) - round(abs_x);
290 layout.size.height = round(abs_y + layout.size.height) - round(abs_y);
291
292 let child_count = tree.child_count(node);
293 for index in 0..child_count {
294 let child = tree.child(node, index);
295 round_layout(tree, child, abs_x, abs_y);
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::perform_hidden_layout;
302 use crate::geometry::{Point, Size};
303 use crate::style::{Display, Style};
304 use crate::Taffy;
305
306 #[test]
307 fn hidden_layout_should_hide_recursively() {
308 let mut taffy = Taffy::new();
309
310 let style: Style = Style { display: Display::Flex, size: Size::from_points(50.0, 50.0), ..Default::default() };
311
312 let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
313 let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
314 let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
315
316 let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
317 let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
318
319 let root = taffy
320 .new_with_children(
321 Style { display: Display::None, size: Size::from_points(50.0, 50.0), ..Default::default() },
322 &[child_00, child_01],
323 )
324 .unwrap();
325
326 perform_hidden_layout(&mut taffy, root.into());
327
328 // Whatever size and display-mode the nodes had previously,
329 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
330 for (node, _) in taffy.nodes.iter().filter(|(node, _)| *node != root.into()) {
331 if let Ok(layout) = taffy.layout(node.into()) {
332 assert_eq!(layout.size, Size::zero());
333 assert_eq!(layout.location, Point::zero());
334 }
335 }
336 }
337}