Export BfcSlot and add changelog entry
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 = "float_layout")]
31pub(crate) mod float;
32
33#[cfg(feature = "flexbox")]
34pub(crate) mod flexbox;
35
36#[cfg(feature = "grid")]
37pub(crate) mod grid;
38
39pub use leaf::compute_leaf_layout;
40
41#[cfg(feature = "block_layout")]
42pub use self::block::{compute_block_layout, BlockContext, BlockFormattingContext};
43
44#[cfg(feature = "flexbox")]
45pub use self::flexbox::compute_flexbox_layout;
46
47#[cfg(feature = "grid")]
48pub use self::grid::compute_grid_layout;
49
50#[cfg(feature = "float_layout")]
51pub use self::float::{BfcSlot, ContentSlot, FloatContext, FloatIntrinsicWidthCalculator};
52
53use crate::geometry::{Line, Point, Size};
54use crate::style::{AvailableSpace, CoreStyle, Overflow};
55use crate::tree::{
56 Layout, LayoutInput, LayoutOutput, LayoutPartialTree, LayoutPartialTreeExt, NodeId, RoundTree, SizingMode,
57};
58use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
59use crate::util::sys::round;
60use crate::util::ResolveOrZero;
61use crate::{CacheTree, MaybeMath, MaybeResolve};
62
63/// Compute layout for the root node in the tree
64pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
65 let mut known_dimensions = Size::NONE;
66
67 #[cfg(feature = "block_layout")]
68 {
69 use crate::BoxSizing;
70
71 let parent_size = available_space.into_options();
72 let style = tree.get_core_container_style(root);
73
74 if style.is_block() {
75 // Pull these out earlier to avoid borrowing issues
76 let aspect_ratio = style.aspect_ratio();
77 let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
78 let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
79 let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
80 let padding_border_size = (padding + border).sum_axes();
81 let box_sizing_adjustment =
82 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
83
84 let min_size = style
85 .min_size()
86 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
87 .maybe_apply_aspect_ratio(aspect_ratio)
88 .maybe_add(box_sizing_adjustment);
89 let max_size = style
90 .max_size()
91 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
92 .maybe_apply_aspect_ratio(aspect_ratio)
93 .maybe_add(box_sizing_adjustment);
94 let clamped_style_size = style
95 .size()
96 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
97 .maybe_apply_aspect_ratio(aspect_ratio)
98 .maybe_add(box_sizing_adjustment)
99 .maybe_clamp(min_size, max_size);
100
101 // If both min and max in a given axis are set and max <= min then this determines the size in that axis
102 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
103 (Some(min), Some(max)) if max <= min => Some(min),
104 _ => None,
105 });
106
107 // Block nodes automatically stretch fit their width to fit available space if available space is definite
108 let available_space_based_size = Size {
109 width: available_space.width.into_option().maybe_sub(margin.horizontal_axis_sum()),
110 height: None,
111 };
112
113 let styled_based_known_dimensions = known_dimensions
114 .or(min_max_definite_size)
115 .or(clamped_style_size)
116 .or(available_space_based_size)
117 .maybe_max(padding_border_size);
118
119 known_dimensions = styled_based_known_dimensions;
120 }
121 }
122
123 // Recursively compute node layout
124 let output = tree.perform_child_layout(
125 root,
126 known_dimensions,
127 available_space.into_options(),
128 available_space,
129 SizingMode::InherentSize,
130 Line::FALSE,
131 );
132 let style = tree.get_core_container_style(root);
133 let padding =
134 style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
135 let border =
136 style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
137 let margin =
138 style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
139 let scrollbar_size = Size {
140 width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
141 height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
142 };
143 let location = Point {
144 x: if style.direction().is_rtl() {
145 available_space.width.into_option().map_or(0.0, |available_width| available_width - output.size.width)
146 } else {
147 0.0
148 },
149 y: 0.0,
150 };
151 drop(style);
152
153 tree.set_unrounded_layout(
154 root,
155 &Layout {
156 order: 0,
157 location,
158 size: output.size,
159 #[cfg(feature = "content_size")]
160 content_size: output.content_size,
161 scrollbar_size,
162 padding,
163 border,
164 // TODO: support auto margins for root node?
165 margin,
166 },
167 );
168}
169
170/// Attempts to find a cached layout for the specified node and layout inputs.
171///
172/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
173#[inline(always)]
174pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
175 tree: &mut Tree,
176 node: NodeId,
177 inputs: LayoutInput,
178 compute_uncached: ComputeFunction,
179) -> LayoutOutput
180where
181 ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
182{
183 debug_push_node!(node);
184
185 // First we check if we have a cached result for the given input
186 let cache_entry = tree.cache_get(node, &inputs);
187 if let Some(cached_size_and_baselines) = cache_entry {
188 debug_log_node!(inputs);
189 debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
190 debug_pop_node!();
191 return cached_size_and_baselines;
192 }
193
194 debug_log_node!(inputs);
195
196 let computed_size_and_baselines = compute_uncached(tree, node, inputs);
197
198 // Cache result
199 tree.cache_store(node, &inputs, computed_size_and_baselines);
200
201 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
202 debug_pop_node!();
203
204 computed_size_and_baselines
205}
206
207/// Rounds the calculated layout to exact pixel values
208///
209/// In order to ensure that no gaps in the layout are introduced we:
210/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
211/// parent-relative coordinates
212/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
213/// rather than rounding the width/height directly
214///
215/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
216///
217/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
218/// and write to `final_layout`.
219pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
220 return round_layout_inner(tree, node_id, 0.0, 0.0);
221
222 /// Recursive function to apply rounding to all descendents
223 fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
224 let unrounded_layout = tree.get_unrounded_layout(node_id);
225 let mut layout = unrounded_layout;
226
227 let cumulative_x = cumulative_x + unrounded_layout.location.x;
228 let cumulative_y = cumulative_y + unrounded_layout.location.y;
229
230 layout.location.x = round(unrounded_layout.location.x);
231 layout.location.y = round(unrounded_layout.location.y);
232 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
233 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
234 layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
235 layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
236 layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
237 layout.border.right = round(cumulative_x + unrounded_layout.size.width)
238 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
239 layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
240 layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
241 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
242 layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
243 layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
244 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
245 layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
246 layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
247 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
248
249 #[cfg(feature = "content_size")]
250 round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
251
252 tree.set_final_layout(node_id, &layout);
253
254 let child_count = tree.child_count(node_id);
255 for index in 0..child_count {
256 let child = tree.get_child_id(node_id, index);
257 round_layout_inner(tree, child, cumulative_x, cumulative_y);
258 }
259 }
260
261 #[cfg(feature = "content_size")]
262 #[inline(always)]
263 /// Round content size variables.
264 /// This is split into a separate function to make it easier to feature flag.
265 fn round_content_size(
266 layout: &mut Layout,
267 unrounded_content_size: Size<f32>,
268 cumulative_x: f32,
269 cumulative_y: f32,
270 ) {
271 layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
272 layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
273 }
274}
275
276/// Creates a layout for this node and its children, recursively.
277/// Each hidden node has zero size and is placed at the origin
278pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
279 // Clear cache and set zeroed-out layout for the node
280 tree.cache_clear(node);
281 tree.set_unrounded_layout(node, &Layout::with_order(0));
282
283 // Perform hidden layout on all children
284 for index in 0..tree.child_count(node) {
285 let child_id = tree.get_child_id(node, index);
286 tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
287 }
288
289 LayoutOutput::HIDDEN
290}
291
292/// A module for unified re-exports of detailed layout info structs, used by low level API
293#[cfg(feature = "detailed_layout_info")]
294pub mod detailed_info {
295 #[cfg(feature = "grid")]
296 pub use super::grid::{DetailedGridInfo, DetailedGridItemsInfo, DetailedGridTracksInfo};
297}
298
299#[cfg(test)]
300mod tests {
301 use super::compute_hidden_layout;
302 use crate::geometry::{Point, Size};
303 use crate::style::{Display, Style};
304 use crate::TaffyTree;
305
306 #[test]
307 fn hidden_layout_should_hide_recursively() {
308 let mut taffy: TaffyTree<()> = TaffyTree::new();
309
310 let style: Style = Style { display: Display::Flex, size: Size::from_lengths(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_lengths(50.0, 50.0), ..Default::default() },
322 &[child_00, child_01],
323 )
324 .unwrap();
325
326 compute_hidden_layout(&mut taffy.as_layout_tree(), root);
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
331 for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
332 let layout = taffy.layout(node).unwrap();
333 assert_eq!(layout.size, Size::zero());
334 assert_eq!(layout.location, Point::zero());
335 }
336 }
337}