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