Remove `LayoutAlgorithm` trait (#562)
1//! Computation specific for the default `Taffy` tree implementation
2use crate::compute::leaf::compute_leaf_layout;
3use crate::geometry::{Line, Point, Size};
4use crate::style::{AvailableSpace, Display};
5use crate::tree::{
6 Layout, LayoutInput, LayoutOutput, LayoutTree, NodeId, RunMode, SizingMode, Taffy, TaffyError, TaffyView,
7};
8use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
9use crate::util::sys::round;
10
11#[cfg(feature = "block_layout")]
12use crate::compute::compute_block_layout;
13#[cfg(feature = "flexbox")]
14use crate::compute::compute_flexbox_layout;
15#[cfg(feature = "grid")]
16use crate::compute::compute_grid_layout;
17
18/// Updates the stored layout of the provided `node` and its children
19pub(crate) fn compute_layout<NodeContext, MeasureFunction>(
20 taffy_view: &mut TaffyView<NodeContext, MeasureFunction>,
21 root: NodeId,
22 available_space: Size<AvailableSpace>,
23) -> Result<(), TaffyError>
24where
25 MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
26{
27 // Recursively compute node layout
28 let size_and_baselines = compute_node_layout(
29 taffy_view,
30 root,
31 LayoutInput {
32 known_dimensions: Size::NONE,
33 parent_size: available_space.into_options(),
34 available_space,
35 sizing_mode: SizingMode::InherentSize,
36 run_mode: RunMode::PerformLayout,
37 vertical_margins_are_collapsible: Line::FALSE,
38 },
39 );
40
41 let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
42 *taffy_view.layout_mut(root) = layout;
43
44 // If rounding is enabled, recursively round the layout's of this node and all children
45 if taffy_view.taffy.config.use_rounding {
46 round_layout(taffy_view.taffy, root, 0.0, 0.0);
47 }
48
49 Ok(())
50}
51
52/// Updates the stored layout of the provided `node` and its children
53pub(crate) fn compute_node_layout<NodeContext, MeasureFunction>(
54 taffy_view: &mut TaffyView<NodeContext, MeasureFunction>,
55 node: NodeId,
56 inputs: LayoutInput,
57) -> LayoutOutput
58where
59 MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
60{
61 debug_push_node!(node);
62
63 let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
64 let node_key = node.into();
65 let has_children = !taffy_view.taffy.children[node_key].is_empty();
66
67 // First we check if we have a cached result for the given input
68 let cache_run_mode = if !has_children { RunMode::PerformLayout } else { run_mode };
69 let cache = &taffy_view.taffy.nodes[node_key].cache;
70 let cache_entry = cache.get(known_dimensions, available_space, cache_run_mode);
71 if let Some(cached_size_and_baselines) = cache_entry {
72 debug_log!("CACHE", dbg:cached_size_and_baselines.size);
73 debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
74 debug_pop_node!();
75 return cached_size_and_baselines;
76 }
77
78 // Fetch `Display` style (+ debug log)
79 let display_mode = taffy_view.taffy.nodes[node_key].style.display;
80 debug_log!(display_mode);
81 debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
82
83 let computed_size_and_baselines = match (display_mode, has_children) {
84 (Display::None, _) => perform_taffy_tree_hidden_layout(taffy_view.taffy, node),
85 #[cfg(feature = "block_layout")]
86 (Display::Block, true) => compute_block_layout(taffy_view, node, inputs),
87 #[cfg(feature = "flexbox")]
88 (Display::Flex, true) => compute_flexbox_layout(taffy_view, node, inputs),
89 #[cfg(feature = "grid")]
90 (Display::Grid, true) => compute_grid_layout(taffy_view, node, inputs),
91 (_, false) => compute_leaf_layout(
92 &mut taffy_view.measure_function,
93 node,
94 inputs,
95 &taffy_view.taffy.nodes[node_key].style,
96 taffy_view.taffy.nodes[node_key].needs_measure.then(|| &mut taffy_view.taffy.node_context_data[node_key]),
97 ),
98 };
99
100 // Cache result
101 taffy_view.taffy.nodes[node_key].cache.store(
102 known_dimensions,
103 available_space,
104 cache_run_mode,
105 computed_size_and_baselines,
106 );
107
108 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
109 debug_pop_node!();
110
111 computed_size_and_baselines
112}
113
114/// Creates a layout for this node and its children, recursively.
115/// Each hidden node has zero size and is placed at the origin
116fn perform_taffy_tree_hidden_layout<NodeContext>(tree: &mut Taffy<NodeContext>, node: NodeId) -> LayoutOutput {
117 /// Recursive function to apply hidden layout to all descendents
118 fn perform_hidden_layout_inner<NodeContext>(tree: &mut Taffy<NodeContext>, node: NodeId, order: u32) {
119 let node_key = node.into();
120 tree.nodes[node_key].unrounded_layout = Layout::with_order(order);
121 tree.nodes[node_key].final_layout = Layout::with_order(order);
122 tree.nodes[node_key].cache.clear();
123 for order in 0..tree.children[node_key].len() {
124 perform_hidden_layout_inner(tree, tree.children[node_key][order], order as _);
125 }
126 }
127
128 let node_key = node.into();
129 for order in 0..tree.children[node.into()].len() {
130 perform_hidden_layout_inner(tree, tree.children[node_key][order], order as _);
131 }
132
133 LayoutOutput::HIDDEN
134}
135
136/// Rounds the calculated [`Layout`] to exact pixel values
137///
138/// In order to ensure that no gaps in the layout are introduced we:
139/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
140/// parent-relative coordinates
141/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
142/// rather than rounding the width/height directly
143/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
144///
145/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
146/// and write to `final_layout`.
147fn round_layout<NodeContext>(tree: &mut Taffy<NodeContext>, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
148 let node = &mut tree.nodes[node_id.into()];
149 let unrounded_layout = node.unrounded_layout;
150 let layout = &mut node.final_layout;
151
152 let cumulative_x = cumulative_x + unrounded_layout.location.x;
153 let cumulative_y = cumulative_y + unrounded_layout.location.y;
154
155 layout.location.x = round(unrounded_layout.location.x);
156 layout.location.y = round(unrounded_layout.location.y);
157 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
158 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
159
160 let child_count = tree.child_count(node_id).unwrap();
161 for index in 0..child_count {
162 let child = tree.children[node_id.into()][index];
163 round_layout(tree, child, cumulative_x, cumulative_y);
164 }
165}