tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 9TOML 3CSS 1345 Definitions

⎇ 9e6b0bd4179f932451cf51d7d8e53cc09b3c4a3f src/compute/mod.rs Rust · 373 lines · 17.9 KB History Who touched what raw

Fix build when block_layout is enabled bu float_layout isn't 9e6b0bd41 · Nico Burns ·

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::{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/// An empty "slot" that avoids floats that is suitable for non-floated content
64/// to be laid out into
65#[derive(Debug, Clone, Copy, Default)]
66#[cfg(any(feature = "float_layout", feature = "block_layout"))]
67pub struct ContentSlot {
68    /// The id of the segment that the slot starts in
69    pub segment_id: Option<usize>,
70    /// The x position of the start of the slot
71    pub x: f32,
72    /// The y position of the start of the slot
73    pub y: f32,
74    /// The width of the slot
75    pub width: f32,
76    /// The height of the slot
77    pub height: f32,
78}
79
80impl ContentSlot {
81    /// The distance of the left edge of the slot from the left side of it's container
82    #[inline(always)]
83    pub fn left_edge(&self) -> f32 {
84        self.x
85    }
86    /// The distance of the right edge of the slot from the left side of it's container
87    #[inline(always)]
88    pub fn right_edge(&self) -> f32 {
89        self.x + self.width
90    }
91    /// The distance of the top edge of the slot from the top side of it's container
92    #[inline(always)]
93    pub fn top_edge(&self) -> f32 {
94        self.y + self.height
95    }
96    /// The distance of the bottom edge of the slot from the top side of it's container
97    #[inline(always)]
98    pub fn bottom_edge(&self) -> f32 {
99        self.y + self.height
100    }
101}
102
103/// Compute layout for the root node in the tree
104pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
105    let mut known_dimensions = Size::NONE;
106
107    #[cfg(feature = "block_layout")]
108    {
109        use crate::BoxSizing;
110
111        let parent_size = available_space.into_options();
112        let style = tree.get_core_container_style(root);
113
114        if style.is_block() {
115            // Pull these out earlier to avoid borrowing issues
116            let aspect_ratio = style.aspect_ratio();
117            let margin = style.margin().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
118            let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
119            let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
120            let padding_border_size = (padding + border).sum_axes();
121            let box_sizing_adjustment =
122                if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
123
124            let min_size = style
125                .min_size()
126                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
127                .maybe_apply_aspect_ratio(aspect_ratio)
128                .maybe_add(box_sizing_adjustment);
129            let max_size = style
130                .max_size()
131                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
132                .maybe_apply_aspect_ratio(aspect_ratio)
133                .maybe_add(box_sizing_adjustment);
134            let clamped_style_size = style
135                .size()
136                .maybe_resolve(parent_size, |val, basis| tree.calc(val, basis))
137                .maybe_apply_aspect_ratio(aspect_ratio)
138                .maybe_add(box_sizing_adjustment)
139                .maybe_clamp(min_size, max_size);
140
141            // If both min and max in a given axis are set and max <= min then this determines the size in that axis
142            let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
143                (Some(min), Some(max)) if max <= min => Some(min),
144                _ => None,
145            });
146
147            // Block nodes automatically stretch fit their width to fit available space if available space is definite
148            let available_space_based_size = Size {
149                width: available_space.width.into_option().maybe_sub(margin.horizontal_axis_sum()),
150                height: None,
151            };
152
153            let styled_based_known_dimensions = known_dimensions
154                .or(min_max_definite_size)
155                .or(clamped_style_size)
156                .or(available_space_based_size)
157                .maybe_max(padding_border_size);
158
159            known_dimensions = styled_based_known_dimensions;
160        }
161    }
162
163    let style = tree.get_core_container_style(root);
164    let padding =
165        style.padding().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
166    let border =
167        style.border().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
168    let margin =
169        style.margin().resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis));
170    let scrollbar_size = Size {
171        width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
172        height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
173    };
174    let direction = style.direction();
175    drop(style);
176
177    // Recursively compute node layout
178    let output = tree.perform_child_layout(
179        root,
180        known_dimensions,
181        available_space.into_options(),
182        available_space,
183        SizingMode::InherentSize,
184        direction,
185        Line::FALSE,
186    );
187
188    tree.set_unrounded_layout(
189        root,
190        &Layout {
191            order: 0,
192            location: Point::ZERO,
193            size: output.size,
194            #[cfg(feature = "content_size")]
195            content_size: output.content_size,
196            scrollbar_size,
197            padding,
198            border,
199            // TODO: support auto margins for root node?
200            margin,
201        },
202    );
203}
204
205/// Attempts to find a cached layout for the specified node and layout inputs.
206///
207/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
208#[inline(always)]
209pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
210    tree: &mut Tree,
211    node: NodeId,
212    inputs: LayoutInput,
213    compute_uncached: ComputeFunction,
214) -> LayoutOutput
215where
216    ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
217{
218    debug_push_node!(node);
219    let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
220
221    // First we check if we have a cached result for the given input
222    let cache_entry = tree.cache_get(node, known_dimensions, available_space, run_mode);
223    if let Some(cached_size_and_baselines) = cache_entry {
224        debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
225        debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
226        debug_pop_node!();
227        return cached_size_and_baselines;
228    }
229
230    debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
231
232    let computed_size_and_baselines = compute_uncached(tree, node, inputs);
233
234    // Cache result
235    tree.cache_store(node, known_dimensions, available_space, run_mode, computed_size_and_baselines);
236
237    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
238    debug_pop_node!();
239
240    computed_size_and_baselines
241}
242
243/// Rounds the calculated layout to exact pixel values
244///
245/// In order to ensure that no gaps in the layout are introduced we:
246///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
247///     parent-relative coordinates
248///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
249///     rather than rounding the width/height directly
250///
251/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
252///
253/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
254/// and write to `final_layout`.
255pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
256    return round_layout_inner(tree, node_id, 0.0, 0.0);
257
258    /// Recursive function to apply rounding to all descendents
259    fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
260        let unrounded_layout = tree.get_unrounded_layout(node_id);
261        let mut layout = unrounded_layout;
262
263        let cumulative_x = cumulative_x + unrounded_layout.location.x;
264        let cumulative_y = cumulative_y + unrounded_layout.location.y;
265
266        layout.location.x = round(unrounded_layout.location.x);
267        layout.location.y = round(unrounded_layout.location.y);
268        layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
269        layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
270        layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
271        layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
272        layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
273        layout.border.right = round(cumulative_x + unrounded_layout.size.width)
274            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
275        layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
276        layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
277            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
278        layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
279        layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
280            - round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
281        layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
282        layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
283            - round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
284
285        #[cfg(feature = "content_size")]
286        round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
287
288        tree.set_final_layout(node_id, &layout);
289
290        let child_count = tree.child_count(node_id);
291        for index in 0..child_count {
292            let child = tree.get_child_id(node_id, index);
293            round_layout_inner(tree, child, cumulative_x, cumulative_y);
294        }
295    }
296
297    #[cfg(feature = "content_size")]
298    #[inline(always)]
299    /// Round content size variables.
300    /// This is split into a separate function to make it easier to feature flag.
301    fn round_content_size(
302        layout: &mut Layout,
303        unrounded_content_size: Size<f32>,
304        cumulative_x: f32,
305        cumulative_y: f32,
306    ) {
307        layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
308        layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
309    }
310}
311
312/// Creates a layout for this node and its children, recursively.
313/// Each hidden node has zero size and is placed at the origin
314pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
315    // Clear cache and set zeroed-out layout for the node
316    tree.cache_clear(node);
317    tree.set_unrounded_layout(node, &Layout::with_order(0));
318
319    // Perform hidden layout on all children
320    for index in 0..tree.child_count(node) {
321        let child_id = tree.get_child_id(node, index);
322        tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
323    }
324
325    LayoutOutput::HIDDEN
326}
327
328/// A module for unified re-exports of detailed layout info structs, used by low level API
329#[cfg(feature = "detailed_layout_info")]
330pub mod detailed_info {
331    #[cfg(feature = "grid")]
332    pub use super::grid::{DetailedGridInfo, DetailedGridItemsInfo, DetailedGridTracksInfo};
333}
334
335#[cfg(test)]
336mod tests {
337    use super::compute_hidden_layout;
338    use crate::geometry::{Point, Size};
339    use crate::style::{Display, Style};
340    use crate::TaffyTree;
341
342    #[test]
343    fn hidden_layout_should_hide_recursively() {
344        let mut taffy: TaffyTree<()> = TaffyTree::new();
345
346        let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
347
348        let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
349        let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
350        let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
351
352        let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
353        let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
354
355        let root = taffy
356            .new_with_children(
357                Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
358                &[child_00, child_01],
359            )
360            .unwrap();
361
362        compute_hidden_layout(&mut taffy.as_layout_tree(), root);
363
364        // Whatever size and display-mode the nodes had previously,
365        // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
366
367        for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
368            let layout = taffy.layout(node).unwrap();
369            assert_eq!(layout.size, Size::zero());
370            assert_eq!(layout.location, Point::zero());
371        }
372    }
373}