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