Add `taffy_tree` feature to opt-out of default Taffy tree (#448)
1//! Computation specific for the default `Taffy` tree implementation
2
3use crate::{
4 compute::{
5 leaf::{measure_size, perform_layout},
6 HiddenAlgorithm,
7 },
8 geometry::Point,
9 prelude::{Layout, NodeId, Size},
10 style::{AvailableSpace, Display},
11 tree::{RunMode, SizeAndBaselines, SizingMode},
12 util::sys::round,
13 CssGridAlgorithm, FlexboxAlgorithm, LayoutAlgorithm, LayoutTree, Taffy, TaffyError,
14};
15
16/// Updates the stored layout of the provided `node` and its children
17pub(crate) fn compute_layout(
18 taffy: &mut Taffy,
19 root: NodeId,
20 available_space: Size<AvailableSpace>,
21) -> Result<(), TaffyError> {
22 // Recursively compute node layout
23 let size_and_baselines = perform_node_layout(
24 taffy,
25 root,
26 Size::NONE,
27 available_space.into_options(),
28 available_space,
29 SizingMode::InherentSize,
30 );
31
32 let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
33 *taffy.layout_mut(root) = layout;
34
35 // If rounding is enabled, recursively round the layout's of this node and all children
36 if taffy.config.use_rounding {
37 round_layout(taffy, root, 0.0, 0.0);
38 }
39
40 Ok(())
41}
42
43/// Perform full layout on a node. Chooses which algorithm to use based on the `display` property.
44pub(crate) fn perform_node_layout(
45 tree: &mut Taffy,
46 node: NodeId,
47 known_dimensions: Size<Option<f32>>,
48 parent_size: Size<Option<f32>>,
49 available_space: Size<AvailableSpace>,
50 sizing_mode: SizingMode,
51) -> SizeAndBaselines {
52 compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::PeformLayout, sizing_mode)
53}
54
55/// Measure a node's size. Chooses which algorithm to use based on the `display` property.
56pub(crate) fn measure_node_size(
57 tree: &mut Taffy,
58 node: NodeId,
59 known_dimensions: Size<Option<f32>>,
60 parent_size: Size<Option<f32>>,
61 available_space: Size<AvailableSpace>,
62 sizing_mode: SizingMode,
63) -> Size<f32> {
64 compute_node_layout(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize, sizing_mode)
65 .size
66}
67
68/// Updates the stored layout of the provided `node` and its children
69fn compute_node_layout(
70 tree: &mut Taffy,
71 node: NodeId,
72 known_dimensions: Size<Option<f32>>,
73 parent_size: Size<Option<f32>>,
74 available_space: Size<AvailableSpace>,
75 run_mode: RunMode,
76 sizing_mode: SizingMode,
77) -> SizeAndBaselines {
78 #[cfg(any(feature = "debug", feature = "profile"))]
79 NODE_LOGGER.push_node(node);
80 #[cfg(feature = "debug")]
81 println!();
82
83 let node_key = node.into();
84 let has_children = !tree.children[node_key].is_empty();
85
86 // First we check if we have a cached result for the given input
87 let cache_run_mode = if !has_children { RunMode::PeformLayout } else { run_mode };
88 if let Some(cached_size_and_baselines) =
89 tree.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode)
90 {
91 #[cfg(feature = "debug")]
92 NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
93 #[cfg(feature = "debug")]
94 debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
95 #[cfg(any(feature = "debug", feature = "profile"))]
96 NODE_LOGGER.pop_node();
97 return cached_size_and_baselines;
98 }
99
100 #[cfg(feature = "debug")]
101 debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
102
103 /// Inlined function generic over the LayoutAlgorithm to reduce code duplication
104 #[inline(always)]
105 fn perform_computations<Algorithm: LayoutAlgorithm>(
106 tree: &mut impl LayoutTree,
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(feature = "debug")]
115 NODE_LOGGER.log(Algorithm::NAME);
116
117 match run_mode {
118 RunMode::PeformLayout => {
119 Algorithm::perform_layout(tree, node, known_dimensions, parent_size, available_space, sizing_mode)
120 }
121 RunMode::ComputeSize => {
122 Algorithm::measure_size(tree, node, known_dimensions, parent_size, available_space, sizing_mode).into()
123 }
124 }
125 }
126
127 let display_mode = tree.nodes[node_key].style.display;
128 let computed_size_and_baselines = match (display_mode, has_children) {
129 (Display::None, _) => perform_computations::<HiddenAlgorithm>(
130 tree,
131 node,
132 known_dimensions,
133 parent_size,
134 available_space,
135 run_mode,
136 sizing_mode,
137 ),
138 #[cfg(feature = "flexbox")]
139 (Display::Flex, true) => perform_computations::<FlexboxAlgorithm>(
140 tree,
141 node,
142 known_dimensions,
143 parent_size,
144 available_space,
145 run_mode,
146 sizing_mode,
147 ),
148 #[cfg(feature = "grid")]
149 (Display::Grid, true) => perform_computations::<CssGridAlgorithm>(
150 tree,
151 node,
152 known_dimensions,
153 parent_size,
154 available_space,
155 run_mode,
156 sizing_mode,
157 ),
158 (_, false) => match run_mode {
159 RunMode::PeformLayout => perform_layout(
160 &tree.nodes[node_key].style,
161 tree.nodes[node_key].needs_measure.then(|| &tree.measure_funcs[node_key]),
162 known_dimensions,
163 parent_size,
164 available_space,
165 sizing_mode,
166 ),
167 RunMode::ComputeSize => measure_size(
168 &tree.nodes[node_key].style,
169 tree.nodes[node_key].needs_measure.then(|| &tree.measure_funcs[node_key]),
170 known_dimensions,
171 parent_size,
172 available_space,
173 sizing_mode,
174 )
175 .into(),
176 },
177 };
178
179 // Cache result
180 tree.nodes[node_key].cache.store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
181
182 #[cfg(feature = "debug")]
183 NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
184 #[cfg(any(feature = "debug", feature = "profile"))]
185 NODE_LOGGER.pop_node();
186
187 computed_size_and_baselines
188}
189
190/// Rounds the calculated [`Layout`] to exact pixel values
191/// In order to ensure that no gaps in the layout are introduced we:
192/// - Always round based on the absolute coordinates rather than parent-relative coordinates
193/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
194/// rather than rounding the width/height directly
195///
196/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
197fn round_layout(tree: &mut impl LayoutTree, node: NodeId, abs_x: f32, abs_y: f32) {
198 let layout = tree.layout_mut(node);
199 let abs_x = abs_x + layout.location.x;
200 let abs_y = abs_y + layout.location.y;
201
202 layout.location.x = round(layout.location.x);
203 layout.location.y = round(layout.location.y);
204 layout.size.width = round(abs_x + layout.size.width) - round(abs_x);
205 layout.size.height = round(abs_y + layout.size.height) - round(abs_y);
206
207 let child_count = tree.child_count(node);
208 for index in 0..child_count {
209 let child = tree.child(node, index);
210 round_layout(tree, child, abs_x, abs_y);
211 }
212}