tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 9TOML 3CSS 1345 Definiciones

⎇ 22a79ba0ee895068d1c4b0598cbec5f479561b17 src/compute/mod.rs Rust · 331 líneas · 16.5 KB Historia Quién tocó qué crudo

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