Fix build when block_layout is enabled bu float_layout isn't
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::{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/// An empty "slot" that avoids floats that is suitable for non-floated content
64/// to be laid out into
65#[derive(Debug, Clone, Copy, Default)]
66#[cfg(any(feature = "float_layout", feature = "block_layout"))]
67pub struct ContentSlot {
68 /// The id of the segment that the slot starts in
69 pub segment_id: Option<usize>,
70 /// The x position of the start of the slot
71 pub x: f32,
72 /// The y position of the start of the slot
73 pub y: f32,
74 /// The width of the slot
75 pub width: f32,
76 /// The height of the slot
77 pub height: f32,
78}
79
80impl ContentSlot {
81 /// The distance of the left edge of the slot from the left side of it's container
82 #[inline(always)]
83 pub fn left_edge(&self) -> f32 {
84 self.x
85 }
86 /// The distance of the right edge of the slot from the left side of it's container
87 #[inline(always)]
88 pub fn right_edge(&self) -> f32 {
89 self.x + self.width
90 }
91 /// The distance of the top edge of the slot from the top side of it's container
92 #[inline(always)]
93 pub fn top_edge(&self) -> f32 {
94 self.y + self.height
95 }
96 /// The distance of the bottom edge of the slot from the top side of it's container
97 #[inline(always)]
98 pub fn bottom_edge(&self) -> f32 {
99 self.y + self.height
100 }
101}
102
103/// Compute layout for the root node in the tree
104pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
105 let mut known_dimensions = Size::NONE;
106
107 #[cfg(feature = "block_layout")]
108 {
109 use crate::BoxSizing;
110
111 let parent_size = available_space.into_options();
112 let style = tree.get_core_container_style(root);
113
114 if style.is_block() {
115 // Pull these out earlier to avoid borrowing issues
116 let aspect_ratio = style.aspect_ratio();
117 let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
118 let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
119 let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
120 let padding_border_size = (padding + border).sum_axes();
121 let box_sizing_adjustment =
122 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
123
124 let min_size = style
125 .min_size()
126 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
127 .maybe_apply_aspect_ratio(aspect_ratio)
128 .maybe_add(box_sizing_adjustment);
129 let max_size = style
130 .max_size()
131 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
132 .maybe_apply_aspect_ratio(aspect_ratio)
133 .maybe_add(box_sizing_adjustment);
134 let clamped_style_size = style
135 .size()
136 .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
137 .maybe_apply_aspect_ratio(aspect_ratio)
138 .maybe_add(box_sizing_adjustment)
139 .maybe_clamp(min_size, max_size);
140
141 // If both min and max in a given axis are set and max <= min then this determines the size in that axis
142 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
143 (Some(min), Some(max)) if max <= min => Some(min),
144 _ => None,
145 });
146
147 // Block nodes automatically stretch fit their width to fit available space if available space is definite
148 let available_space_based_size = Size {
149 width: available_space.width.into_option().maybe_sub(margin.horizontal_axis_sum()),
150 height: None,
151 };
152
153 let styled_based_known_dimensions = known_dimensions
154 .or(min_max_definite_size)
155 .or(clamped_style_size)
156 .or(available_space_based_size)
157 .maybe_max(padding_border_size);
158
159 known_dimensions = styled_based_known_dimensions;
160 }
161 }
162
163 let style = tree.get_core_container_style(root);
164 let padding =
165 style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
166 let border =
167 style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
168 let margin =
169 style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
170 let scrollbar_size = Size {
171 width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
172 height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
173 };
174 let direction = style.direction();
175 drop(style);
176
177 // Recursively compute node layout
178 let output = tree.perform_child_layout(
179 root,
180 known_dimensions,
181 available_space.into_options(),
182 available_space,
183 SizingMode::InherentSize,
184 direction,
185 Line::FALSE,
186 );
187
188 tree.set_unrounded_layout(
189 root,
190 &Layout {
191 order: 0,
192 location: Point::ZERO,
193 size: output.size,
194 #[cfg(feature = "content_size")]
195 content_size: output.content_size,
196 scrollbar_size,
197 padding,
198 border,
199 // TODO: support auto margins for root node?
200 margin,
201 },
202 );
203}
204
205/// Attempts to find a cached layout for the specified node and layout inputs.
206///
207/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
208#[inline(always)]
209pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
210 tree: &mut Tree,
211 node: NodeId,
212 inputs: LayoutInput,
213 compute_uncached: ComputeFunction,
214) -> LayoutOutput
215where
216 ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
217{
218 debug_push_node!(node);
219 let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
220
221 // First we check if we have a cached result for the given input
222 let cache_entry = tree.cache_get(node, known_dimensions, available_space, run_mode);
223 if let Some(cached_size_and_baselines) = cache_entry {
224 debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
225 debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
226 debug_pop_node!();
227 return cached_size_and_baselines;
228 }
229
230 debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
231
232 let computed_size_and_baselines = compute_uncached(tree, node, inputs);
233
234 // Cache result
235 tree.cache_store(node, known_dimensions, available_space, run_mode, computed_size_and_baselines);
236
237 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
238 debug_pop_node!();
239
240 computed_size_and_baselines
241}
242
243/// Rounds the calculated layout to exact pixel values
244///
245/// In order to ensure that no gaps in the layout are introduced we:
246/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
247/// parent-relative coordinates
248/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
249/// rather than rounding the width/height directly
250///
251/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
252///
253/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
254/// and write to `final_layout`.
255pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
256 return round_layout_inner(tree, node_id, 0.0, 0.0);
257
258 /// Recursive function to apply rounding to all descendents
259 fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
260 let unrounded_layout = tree.get_unrounded_layout(node_id);
261 let mut layout = unrounded_layout;
262
263 let cumulative_x = cumulative_x + unrounded_layout.location.x;
264 let cumulative_y = cumulative_y + unrounded_layout.location.y;
265
266 layout.location.x = round(unrounded_layout.location.x);
267 layout.location.y = round(unrounded_layout.location.y);
268 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
269 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
270 layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
271 layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
272 layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
273 layout.border.right = round(cumulative_x + unrounded_layout.size.width)
274 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
275 layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
276 layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
277 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
278 layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
279 layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
280 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
281 layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
282 layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
283 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
284
285 #[cfg(feature = "content_size")]
286 round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
287
288 tree.set_final_layout(node_id, &layout);
289
290 let child_count = tree.child_count(node_id);
291 for index in 0..child_count {
292 let child = tree.get_child_id(node_id, index);
293 round_layout_inner(tree, child, cumulative_x, cumulative_y);
294 }
295 }
296
297 #[cfg(feature = "content_size")]
298 #[inline(always)]
299 /// Round content size variables.
300 /// This is split into a separate function to make it easier to feature flag.
301 fn round_content_size(
302 layout: &mut Layout,
303 unrounded_content_size: Size<f32>,
304 cumulative_x: f32,
305 cumulative_y: f32,
306 ) {
307 layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
308 layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
309 }
310}
311
312/// Creates a layout for this node and its children, recursively.
313/// Each hidden node has zero size and is placed at the origin
314pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
315 // Clear cache and set zeroed-out layout for the node
316 tree.cache_clear(node);
317 tree.set_unrounded_layout(node, &Layout::with_order(0));
318
319 // Perform hidden layout on all children
320 for index in 0..tree.child_count(node) {
321 let child_id = tree.get_child_id(node, index);
322 tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
323 }
324
325 LayoutOutput::HIDDEN
326}
327
328/// A module for unified re-exports of detailed layout info structs, used by low level API
329#[cfg(feature = "detailed_layout_info")]
330pub mod detailed_info {
331 #[cfg(feature = "grid")]
332 pub use super::grid::{DetailedGridInfo, DetailedGridItemsInfo, DetailedGridTracksInfo};
333}
334
335#[cfg(test)]
336mod tests {
337 use super::compute_hidden_layout;
338 use crate::geometry::{Point, Size};
339 use crate::style::{Display, Style};
340 use crate::TaffyTree;
341
342 #[test]
343 fn hidden_layout_should_hide_recursively() {
344 let mut taffy: TaffyTree<()> = TaffyTree::new();
345
346 let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
347
348 let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
349 let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
350 let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
351
352 let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
353 let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
354
355 let root = taffy
356 .new_with_children(
357 Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
358 &[child_00, child_01],
359 )
360 .unwrap();
361
362 compute_hidden_layout(&mut taffy.as_layout_tree(), root);
363
364 // Whatever size and display-mode the nodes had previously,
365 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
366
367 for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
368 let layout = taffy.layout(node).unwrap();
369 assert_eq!(layout.size, Size::zero());
370 assert_eq!(layout.location, Point::zero());
371 }
372 }
373}