tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 499HTML 245JavaScript 8Markdown 71218 Definiciones

⎇ 230ab9359bba75fdda0ff35319d082e79189046f src/compute/mod.rs Rust · 333 líneas · 16.7 KB Historia Quién tocó qué crudo

implement `direction` property 230ab9359 · Kane Wang ·

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