1//! Low-level access to the layout algorithms themselves. For a higher-level API, see the [`TaffyTree`](crate::TaffyTree) struct.
2//!
3//! ### Layout functions
4//!
5//! The layout functions all take an [`&mut impl LayoutPartialTree`](crate::LayoutPartialTree) parameter, which represents a single container node and it's direct children.
6//!
7//! | Function | Purpose |
8//! | --- | --- |
9//! | [`compute_flexbox_layout`] | Layout a Flexbox container and it's direct children |
10//! | [`compute_grid_layout`] | Layout a CSS Grid container and it's direct children |
11//! | [`compute_block_layout`] | Layout a Block container and it's direct children |
12//! | [`compute_leaf_layout`] | Applies common properties like padding/border/aspect-ratio to a node before deferring to a passed closure to determine it's size. Can be applied to nodes like text or image nodes. |
13//! | [`compute_root_layout`] | Layout the root node of a tree (regardless of it's layout mode). This function is typically called once to begin a layout run. | |
14//! | [`compute_hidden_layout`] | Mark a node as hidden during layout (like `Display::None`) |
15//! | [`compute_cached_layout`] | Attempts to find a cached layout for the specified node and layout inputs. Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found. |
16//!
17//! ### Other functions
18//!
19//! | Function | Requires | Purpose |
20//! | --- | --- | --- |
21//! | [`round_layout`] | [`RoundTree`] | Round a tree of float-valued layouts to integer pixels |
22//! | [`print_tree`](crate::print_tree) | [`PrintTree`](crate::PrintTree) | Print a debug representation of a node tree and it's computed layout |
23//!
24pub(crate) mod common;
25pub(crate) mod leaf;
26
27#[cfg(feature = "block_layout")]
28pub(crate) mod block;
29
30#[cfg(feature = "flexbox")]
31pub(crate) mod flexbox;
32
33#[cfg(feature = "grid")]
34pub(crate) mod grid;
35
36pub use leaf::compute_leaf_layout;
37
38#[cfg(feature = "block_layout")]
39pub use self::block::compute_block_layout;
40
41#[cfg(feature = "flexbox")]
42pub use self::flexbox::compute_flexbox_layout;
43
44#[cfg(feature = "grid")]
45pub use self::grid::compute_grid_layout;
46
47use crate::geometry::{Line, Point, Size};
48use crate::style::{AvailableSpace, Overflow};
49use crate::tree::{
50 Layout, LayoutInput, LayoutOutput, LayoutPartialTree, LayoutPartialTreeExt, NodeId, RoundTree, SizingMode,
51};
52use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
53use crate::util::sys::round;
54use crate::util::ResolveOrZero;
55
56/// Compute layout for the root node in the tree
57pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
58 // Recursively compute node layout
59 let output = tree.perform_child_layout(
60 root,
61 Size::NONE,
62 available_space.into_options(),
63 available_space,
64 SizingMode::InherentSize,
65 Line::FALSE,
66 );
67
68 let style = tree.get_style(root);
69 let padding = style.padding.resolve_or_zero(available_space.width.into_option());
70 let border = style.border.resolve_or_zero(available_space.width.into_option());
71 let scrollbar_size = Size {
72 width: if style.overflow.y == Overflow::Scroll { style.scrollbar_width } else { 0.0 },
73 height: if style.overflow.x == Overflow::Scroll { style.scrollbar_width } else { 0.0 },
74 };
75
76 tree.set_unrounded_layout(
77 root,
78 &Layout {
79 order: 0,
80 location: Point::ZERO,
81 size: output.size,
82 #[cfg(feature = "content_size")]
83 content_size: output.content_size,
84 scrollbar_size,
85 padding,
86 border,
87 },
88 );
89}
90
91/// Attempts to find a cached layout for the specified node and layout inputs.
92///
93/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
94#[inline(always)]
95pub fn compute_cached_layout<Tree: LayoutPartialTree + ?Sized, ComputeFunction>(
96 tree: &mut Tree,
97 node: NodeId,
98 inputs: LayoutInput,
99 mut compute_uncached: ComputeFunction,
100) -> LayoutOutput
101where
102 ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
103{
104 debug_push_node!(node);
105 let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
106
107 // First we check if we have a cached result for the given input
108 let cache_entry = tree.get_cache_mut(node).get(known_dimensions, available_space, run_mode);
109 if let Some(cached_size_and_baselines) = cache_entry {
110 debug_log!("CACHE", dbg:cached_size_and_baselines.size);
111 debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
112 debug_pop_node!();
113 return cached_size_and_baselines;
114 }
115
116 let computed_size_and_baselines = compute_uncached(tree, node, inputs);
117
118 // Cache result
119 tree.get_cache_mut(node).store(known_dimensions, available_space, run_mode, computed_size_and_baselines);
120
121 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
122 debug_pop_node!();
123
124 computed_size_and_baselines
125}
126
127/// Rounds the calculated layout to exact pixel values
128///
129/// In order to ensure that no gaps in the layout are introduced we:
130/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
131/// parent-relative coordinates
132/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
133/// rather than rounding the width/height directly
134/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
135///
136/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
137/// and write to `final_layout`.
138pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
139 return round_layout_inner(tree, node_id, 0.0, 0.0);
140
141 /// Recursive function to apply rounding to all descendents
142 fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
143 let unrounded_layout = *tree.get_unrounded_layout(node_id);
144 let mut layout = unrounded_layout;
145
146 let cumulative_x = cumulative_x + unrounded_layout.location.x;
147 let cumulative_y = cumulative_y + unrounded_layout.location.y;
148
149 layout.location.x = round(unrounded_layout.location.x);
150 layout.location.y = round(unrounded_layout.location.y);
151 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
152 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
153 layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
154 layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
155 layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
156 layout.border.right = round(cumulative_x + unrounded_layout.size.width)
157 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
158 layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
159 layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
160 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
161 layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
162 layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
163 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
164 layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
165 layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
166 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
167
168 #[cfg(feature = "content_size")]
169 round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
170
171 tree.set_final_layout(node_id, &layout);
172
173 let child_count = tree.child_count(node_id);
174 for index in 0..child_count {
175 let child = tree.get_child_id(node_id, index);
176 round_layout_inner(tree, child, cumulative_x, cumulative_y);
177 }
178 }
179
180 #[cfg(feature = "content_size")]
181 #[inline(always)]
182 /// Round content size variables.
183 /// This is split into a separate function to make it easier to feature flag.
184 fn round_content_size(
185 layout: &mut Layout,
186 unrounded_content_size: Size<f32>,
187 cumulative_x: f32,
188 cumulative_y: f32,
189 ) {
190 layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
191 layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
192 }
193}
194
195/// Creates a layout for this node and its children, recursively.
196/// Each hidden node has zero size and is placed at the origin
197pub fn compute_hidden_layout(tree: &mut impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
198 // Clear cache and set zeroed-out layout for the node
199 tree.get_cache_mut(node).clear();
200 tree.set_unrounded_layout(node, &Layout::with_order(0));
201
202 // Perform hidden layout on all children
203 for index in 0..tree.child_count(node) {
204 let child_id = tree.get_child_id(node, index);
205 tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
206 }
207
208 LayoutOutput::HIDDEN
209}
210
211#[cfg(test)]
212mod tests {
213 use super::compute_hidden_layout;
214 use crate::geometry::{Point, Size};
215 use crate::style::{Display, Style};
216 use crate::TaffyTree;
217
218 #[test]
219 fn hidden_layout_should_hide_recursively() {
220 let mut taffy: TaffyTree<()> = TaffyTree::new();
221
222 let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
223
224 let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
225 let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
226 let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
227
228 let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
229 let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
230
231 let root = taffy
232 .new_with_children(
233 Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
234 &[child_00, child_01],
235 )
236 .unwrap();
237
238 compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
239
240 // Whatever size and display-mode the nodes had previously,
241 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
242
243 for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
244 let layout = taffy.layout(node.into()).unwrap();
245 assert_eq!(layout.size, Size::zero());
246 assert_eq!(layout.location, Point::zero());
247 }
248 }
249}