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