implement `direction` property
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::{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 let style = tree.get_core_container_style(root);
124 let padding =
125 style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
126 let border =
127 style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
128 let margin =
129 style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
130 let scrollbar_size = Size {
131 width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
132 height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
133 };
134 let direction = style.direction();
135 drop(style);
136
137 // Recursively compute node layout
138 let output = tree.perform_child_layout(
139 root,
140 known_dimensions,
141 available_space.into_options(),
142 available_space,
143 SizingMode::InherentSize,
144 direction,
145 Line::FALSE,
146 );
147
148 tree.set_unrounded_layout(
149 root,
150 &Layout {
151 order: 0,
152 location: Point::ZERO,
153 size: output.size,
154 #[cfg(feature = "content_size")]
155 content_size: output.content_size,
156 scrollbar_size,
157 padding,
158 border,
159 // TODO: support auto margins for root node?
160 margin,
161 },
162 );
163}
164
165/// Attempts to find a cached layout for the specified node and layout inputs.
166///
167/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
168#[inline(always)]
169pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
170 tree: &mut Tree,
171 node: NodeId,
172 inputs: LayoutInput,
173 compute_uncached: ComputeFunction,
174) -> LayoutOutput
175where
176 ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
177{
178 debug_push_node!(node);
179 let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
180
181 // First we check if we have a cached result for the given input
182 let cache_entry = tree.cache_get(node, known_dimensions, available_space, run_mode);
183 if let Some(cached_size_and_baselines) = cache_entry {
184 debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
185 debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
186 debug_pop_node!();
187 return cached_size_and_baselines;
188 }
189
190 debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
191
192 let computed_size_and_baselines = compute_uncached(tree, node, inputs);
193
194 // Cache result
195 tree.cache_store(node, known_dimensions, available_space, run_mode, computed_size_and_baselines);
196
197 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
198 debug_pop_node!();
199
200 computed_size_and_baselines
201}
202
203/// Rounds the calculated layout to exact pixel values
204///
205/// In order to ensure that no gaps in the layout are introduced we:
206/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
207/// parent-relative coordinates
208/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
209/// rather than rounding the width/height directly
210///
211/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
212///
213/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
214/// and write to `final_layout`.
215pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
216 return round_layout_inner(tree, node_id, 0.0, 0.0);
217
218 /// Recursive function to apply rounding to all descendents
219 fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
220 let unrounded_layout = tree.get_unrounded_layout(node_id);
221 let mut layout = unrounded_layout;
222
223 let cumulative_x = cumulative_x + unrounded_layout.location.x;
224 let cumulative_y = cumulative_y + unrounded_layout.location.y;
225
226 layout.location.x = round(unrounded_layout.location.x);
227 layout.location.y = round(unrounded_layout.location.y);
228 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
229 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
230 layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
231 layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
232 layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
233 layout.border.right = round(cumulative_x + unrounded_layout.size.width)
234 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
235 layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
236 layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
237 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
238 layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
239 layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
240 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
241 layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
242 layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
243 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
244
245 #[cfg(feature = "content_size")]
246 round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
247
248 tree.set_final_layout(node_id, &layout);
249
250 let child_count = tree.child_count(node_id);
251 for index in 0..child_count {
252 let child = tree.get_child_id(node_id, index);
253 round_layout_inner(tree, child, cumulative_x, cumulative_y);
254 }
255 }
256
257 #[cfg(feature = "content_size")]
258 #[inline(always)]
259 /// Round content size variables.
260 /// This is split into a separate function to make it easier to feature flag.
261 fn round_content_size(
262 layout: &mut Layout,
263 unrounded_content_size: Size<f32>,
264 cumulative_x: f32,
265 cumulative_y: f32,
266 ) {
267 layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
268 layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
269 }
270}
271
272/// Creates a layout for this node and its children, recursively.
273/// Each hidden node has zero size and is placed at the origin
274pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
275 // Clear cache and set zeroed-out layout for the node
276 tree.cache_clear(node);
277 tree.set_unrounded_layout(node, &Layout::with_order(0));
278
279 // Perform hidden layout on all children
280 for index in 0..tree.child_count(node) {
281 let child_id = tree.get_child_id(node, index);
282 tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
283 }
284
285 LayoutOutput::HIDDEN
286}
287
288/// A module for unified re-exports of detailed layout info structs, used by low level API
289#[cfg(feature = "detailed_layout_info")]
290pub mod detailed_info {
291 #[cfg(feature = "grid")]
292 pub use super::grid::{DetailedGridInfo, DetailedGridItemsInfo, DetailedGridTracksInfo};
293}
294
295#[cfg(test)]
296mod tests {
297 use super::compute_hidden_layout;
298 use crate::geometry::{Point, Size};
299 use crate::style::{Display, Style};
300 use crate::TaffyTree;
301
302 #[test]
303 fn hidden_layout_should_hide_recursively() {
304 let mut taffy: TaffyTree<()> = TaffyTree::new();
305
306 let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
307
308 let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
309 let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
310 let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
311
312 let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
313 let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
314
315 let root = taffy
316 .new_with_children(
317 Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
318 &[child_00, child_01],
319 )
320 .unwrap();
321
322 compute_hidden_layout(&mut taffy.as_layout_tree(), root);
323
324 // Whatever size and display-mode the nodes had previously,
325 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
326
327 for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
328 let layout = taffy.layout(node).unwrap();
329 assert_eq!(layout.size, Size::zero());
330 assert_eq!(layout.location, Point::zero());
331 }
332 }
333}