Convert algorithms to traitified styles
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, CoreStyle, 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_core_container_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 drop(style);
76
77 tree.set_unrounded_layout(
78 root,
79 &Layout {
80 order: 0,
81 location: Point::ZERO,
82 size: output.size,
83 #[cfg(feature = "content_size")]
84 content_size: output.content_size,
85 scrollbar_size,
86 padding,
87 border,
88 },
89 );
90}
91
92/// Attempts to find a cached layout for the specified node and layout inputs.
93///
94/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
95#[inline(always)]
96pub fn compute_cached_layout<Tree: LayoutPartialTree + ?Sized, ComputeFunction>(
97 tree: &mut Tree,
98 node: NodeId,
99 inputs: LayoutInput,
100 mut compute_uncached: ComputeFunction,
101) -> LayoutOutput
102where
103 ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
104{
105 debug_push_node!(node);
106 let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
107
108 // First we check if we have a cached result for the given input
109 let cache_entry = tree.get_cache_mut(node).get(known_dimensions, available_space, run_mode);
110 if let Some(cached_size_and_baselines) = cache_entry {
111 debug_log!("CACHE", dbg:cached_size_and_baselines.size);
112 debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
113 debug_pop_node!();
114 return cached_size_and_baselines;
115 }
116
117 let computed_size_and_baselines = compute_uncached(tree, node, inputs);
118
119 // Cache result
120 tree.get_cache_mut(node).store(known_dimensions, available_space, run_mode, computed_size_and_baselines);
121
122 debug_log!("RESULT", dbg:computed_size_and_baselines.size);
123 debug_pop_node!();
124
125 computed_size_and_baselines
126}
127
128/// Rounds the calculated layout to exact pixel values
129///
130/// In order to ensure that no gaps in the layout are introduced we:
131/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
132/// parent-relative coordinates
133/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
134/// rather than rounding the width/height directly
135///
136/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
137///
138/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
139/// and write to `final_layout`.
140pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
141 return round_layout_inner(tree, node_id, 0.0, 0.0);
142
143 /// Recursive function to apply rounding to all descendents
144 fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
145 let unrounded_layout = *tree.get_unrounded_layout(node_id);
146 let mut layout = unrounded_layout;
147
148 let cumulative_x = cumulative_x + unrounded_layout.location.x;
149 let cumulative_y = cumulative_y + unrounded_layout.location.y;
150
151 layout.location.x = round(unrounded_layout.location.x);
152 layout.location.y = round(unrounded_layout.location.y);
153 layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
154 layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
155 layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
156 layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
157 layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
158 layout.border.right = round(cumulative_x + unrounded_layout.size.width)
159 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
160 layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
161 layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
162 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
163 layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
164 layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
165 - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
166 layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
167 layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
168 - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
169
170 #[cfg(feature = "content_size")]
171 round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
172
173 tree.set_final_layout(node_id, &layout);
174
175 let child_count = tree.child_count(node_id);
176 for index in 0..child_count {
177 let child = tree.get_child_id(node_id, index);
178 round_layout_inner(tree, child, cumulative_x, cumulative_y);
179 }
180 }
181
182 #[cfg(feature = "content_size")]
183 #[inline(always)]
184 /// Round content size variables.
185 /// This is split into a separate function to make it easier to feature flag.
186 fn round_content_size(
187 layout: &mut Layout,
188 unrounded_content_size: Size<f32>,
189 cumulative_x: f32,
190 cumulative_y: f32,
191 ) {
192 layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
193 layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
194 }
195}
196
197/// Creates a layout for this node and its children, recursively.
198/// Each hidden node has zero size and is placed at the origin
199pub fn compute_hidden_layout(tree: &mut impl LayoutPartialTree, node: NodeId) -> LayoutOutput {
200 // Clear cache and set zeroed-out layout for the node
201 tree.get_cache_mut(node).clear();
202 tree.set_unrounded_layout(node, &Layout::with_order(0));
203
204 // Perform hidden layout on all children
205 for index in 0..tree.child_count(node) {
206 let child_id = tree.get_child_id(node, index);
207 tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
208 }
209
210 LayoutOutput::HIDDEN
211}
212
213#[cfg(test)]
214mod tests {
215 use super::compute_hidden_layout;
216 use crate::geometry::{Point, Size};
217 use crate::style::{Display, Style};
218 use crate::TaffyTree;
219
220 #[test]
221 fn hidden_layout_should_hide_recursively() {
222 let mut taffy: TaffyTree<()> = TaffyTree::new();
223
224 let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
225
226 let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
227 let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
228 let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
229
230 let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
231 let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
232
233 let root = taffy
234 .new_with_children(
235 Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
236 &[child_00, child_01],
237 )
238 .unwrap();
239
240 compute_hidden_layout(&mut taffy.as_layout_tree(), root.into());
241
242 // Whatever size and display-mode the nodes had previously,
243 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
244
245 for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
246 let layout = taffy.layout(node.into()).unwrap();
247 assert_eq!(layout.size, Size::zero());
248 assert_eq!(layout.location, Point::zero());
249 }
250 }
251}