• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //!
24 pub(crate) mod common;
25 pub(crate) mod leaf;
26 
27 #[cfg(feature = "block_layout")]
28 pub(crate) mod block;
29 
30 #[cfg(feature = "flexbox")]
31 pub(crate) mod flexbox;
32 
33 #[cfg(feature = "grid")]
34 pub(crate) mod grid;
35 
36 pub use leaf::compute_leaf_layout;
37 
38 #[cfg(feature = "block_layout")]
39 pub use self::block::compute_block_layout;
40 
41 #[cfg(feature = "flexbox")]
42 pub use self::flexbox::compute_flexbox_layout;
43 
44 #[cfg(feature = "grid")]
45 pub use self::grid::compute_grid_layout;
46 
47 use crate::geometry::{Line, Point, Size};
48 use crate::style::{AvailableSpace, CoreStyle, Overflow};
49 use crate::tree::{
50     Layout, LayoutInput, LayoutOutput, LayoutPartialTree, LayoutPartialTreeExt, NodeId, RoundTree, SizingMode,
51 };
52 use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
53 use crate::util::sys::round;
54 use crate::util::ResolveOrZero;
55 use crate::{BoxSizing, CacheTree, MaybeMath, MaybeResolve};
56 
57 /// Compute layout for the root node in the tree
compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>)58 pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
59     let mut known_dimensions = Size::NONE;
60 
61     #[cfg(feature = "block_layout")]
62     {
63         let parent_size = available_space.into_options();
64         let style = tree.get_core_container_style(root);
65 
66         if style.is_block() {
67             // Pull these out earlier to avoid borrowing issues
68             let aspect_ratio = style.aspect_ratio();
69             let margin = style.margin().resolve_or_zero(parent_size.width);
70             let padding = style.padding().resolve_or_zero(parent_size.width);
71             let border = style.border().resolve_or_zero(parent_size.width);
72             let padding_border_size = (padding + border).sum_axes();
73             let box_sizing_adjustment =
74                 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
75 
76             let min_size = style
77                 .min_size()
78                 .maybe_resolve(parent_size)
79                 .maybe_apply_aspect_ratio(aspect_ratio)
80                 .maybe_add(box_sizing_adjustment);
81             let max_size = style
82                 .max_size()
83                 .maybe_resolve(parent_size)
84                 .maybe_apply_aspect_ratio(aspect_ratio)
85                 .maybe_add(box_sizing_adjustment);
86             let clamped_style_size = style
87                 .size()
88                 .maybe_resolve(parent_size)
89                 .maybe_apply_aspect_ratio(aspect_ratio)
90                 .maybe_add(box_sizing_adjustment)
91                 .maybe_clamp(min_size, max_size);
92 
93             // If both min and max in a given axis are set and max <= min then this determines the size in that axis
94             let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
95                 (Some(min), Some(max)) if max <= min => Some(min),
96                 _ => None,
97             });
98 
99             // Block nodes automatically stretch fit their width to fit available space if available space is definite
100             let available_space_based_size = Size {
101                 width: available_space.width.into_option().maybe_sub(margin.horizontal_axis_sum()),
102                 height: None,
103             };
104 
105             let styled_based_known_dimensions = known_dimensions
106                 .or(min_max_definite_size)
107                 .or(clamped_style_size)
108                 .or(available_space_based_size)
109                 .maybe_max(padding_border_size);
110 
111             known_dimensions = styled_based_known_dimensions;
112         }
113     }
114 
115     // Recursively compute node layout
116     let output = tree.perform_child_layout(
117         root,
118         known_dimensions,
119         available_space.into_options(),
120         available_space,
121         SizingMode::InherentSize,
122         Line::FALSE,
123     );
124 
125     let style = tree.get_core_container_style(root);
126     let padding = style.padding().resolve_or_zero(available_space.width.into_option());
127     let border = style.border().resolve_or_zero(available_space.width.into_option());
128     let margin = style.margin().resolve_or_zero(available_space.width.into_option());
129     let scrollbar_size = Size {
130         width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
131         height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
132     };
133     drop(style);
134 
135     tree.set_unrounded_layout(
136         root,
137         &Layout {
138             order: 0,
139             location: Point::ZERO,
140             size: output.size,
141             #[cfg(feature = "content_size")]
142             content_size: output.content_size,
143             scrollbar_size,
144             padding,
145             border,
146             // TODO: support auto margins for root node?
147             margin,
148         },
149     );
150 }
151 
152 /// Attempts to find a cached layout for the specified node and layout inputs.
153 ///
154 /// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
155 #[inline(always)]
compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>( tree: &mut Tree, node: NodeId, inputs: LayoutInput, mut compute_uncached: ComputeFunction, ) -> LayoutOutput where ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,156 pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
157     tree: &mut Tree,
158     node: NodeId,
159     inputs: LayoutInput,
160     mut compute_uncached: ComputeFunction,
161 ) -> LayoutOutput
162 where
163     ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
164 {
165     debug_push_node!(node);
166     let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
167 
168     // First we check if we have a cached result for the given input
169     let cache_entry = tree.cache_get(node, known_dimensions, available_space, run_mode);
170     if let Some(cached_size_and_baselines) = cache_entry {
171         debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
172         debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
173         debug_pop_node!();
174         return cached_size_and_baselines;
175     }
176 
177     debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
178 
179     let computed_size_and_baselines = compute_uncached(tree, node, inputs);
180 
181     // Cache result
182     tree.cache_store(node, known_dimensions, available_space, run_mode, computed_size_and_baselines);
183 
184     debug_log!("RESULT", dbg:computed_size_and_baselines.size);
185     debug_pop_node!();
186 
187     computed_size_and_baselines
188 }
189 
190 /// Rounds the calculated layout to exact pixel values
191 ///
192 /// In order to ensure that no gaps in the layout are introduced we:
193 ///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
194 ///     parent-relative coordinates
195 ///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
196 ///     rather than rounding the width/height directly
197 ///
198 /// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
199 ///
200 /// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
201 /// and write to `final_layout`.
round_layout(tree: &mut impl RoundTree, node_id: NodeId)202 pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
203     return round_layout_inner(tree, node_id, 0.0, 0.0);
204 
205     /// Recursive function to apply rounding to all descendents
206     fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
207         let unrounded_layout = *tree.get_unrounded_layout(node_id);
208         let mut layout = unrounded_layout;
209 
210         let cumulative_x = cumulative_x + unrounded_layout.location.x;
211         let cumulative_y = cumulative_y + unrounded_layout.location.y;
212 
213         layout.location.x = round(unrounded_layout.location.x);
214         layout.location.y = round(unrounded_layout.location.y);
215         layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
216         layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
217         layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
218         layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
219         layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
220         layout.border.right = round(cumulative_x + unrounded_layout.size.width)
221             - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
222         layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
223         layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
224             - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
225         layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
226         layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
227             - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
228         layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
229         layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
230             - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
231 
232         #[cfg(feature = "content_size")]
233         round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
234 
235         tree.set_final_layout(node_id, &layout);
236 
237         let child_count = tree.child_count(node_id);
238         for index in 0..child_count {
239             let child = tree.get_child_id(node_id, index);
240             round_layout_inner(tree, child, cumulative_x, cumulative_y);
241         }
242     }
243 
244     #[cfg(feature = "content_size")]
245     #[inline(always)]
246     /// Round content size variables.
247     /// This is split into a separate function to make it easier to feature flag.
248     fn round_content_size(
249         layout: &mut Layout,
250         unrounded_content_size: Size<f32>,
251         cumulative_x: f32,
252         cumulative_y: f32,
253     ) {
254         layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
255         layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
256     }
257 }
258 
259 /// Creates a layout for this node and its children, recursively.
260 /// Each hidden node has zero size and is placed at the origin
compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput261 pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
262     // Clear cache and set zeroed-out layout for the node
263     tree.cache_clear(node);
264     tree.set_unrounded_layout(node, &Layout::with_order(0));
265 
266     // Perform hidden layout on all children
267     for index in 0..tree.child_count(node) {
268         let child_id = tree.get_child_id(node, index);
269         tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
270     }
271 
272     LayoutOutput::HIDDEN
273 }
274 
275 /// A module for unified re-exports of detailed layout info structs, used by low level API
276 #[cfg(feature = "detailed_layout_info")]
277 pub mod detailed_info {
278     #[cfg(feature = "grid")]
279     pub use super::grid::DetailedGridInfo;
280 }
281 
282 #[cfg(test)]
283 mod tests {
284     use super::compute_hidden_layout;
285     use crate::geometry::{Point, Size};
286     use crate::style::{Display, Style};
287     use crate::TaffyTree;
288 
289     #[test]
hidden_layout_should_hide_recursively()290     fn hidden_layout_should_hide_recursively() {
291         let mut taffy: TaffyTree<()> = TaffyTree::new();
292 
293         let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
294 
295         let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
296         let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
297         let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
298 
299         let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
300         let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
301 
302         let root = taffy
303             .new_with_children(
304                 Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
305                 &[child_00, child_01],
306             )
307             .unwrap();
308 
309         compute_hidden_layout(&mut taffy.as_layout_tree(), root);
310 
311         // Whatever size and display-mode the nodes had previously,
312         // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
313 
314         for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
315             let layout = taffy.layout(node).unwrap();
316             assert_eq!(layout.size, Size::zero());
317             assert_eq!(layout.location, Point::zero());
318         }
319     }
320 }
321