gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81414 Definitions

⎇ 25ffaee23176000364b17e1b5b29f361a764890a src/compute/taffy_tree.rs Rust · 309 lines · 10.6 KB History Who touched what raw

Fix rounding accumulation bug (#521) 25ffaee23 · Nico Burns ·

1//! Computation specific for the default `Taffy` tree implementation
2
3use crate::compute::{leaf, LayoutAlgorithm};
4use crate::geometry::{Line, Point, Size};
5use crate::style::{AvailableSpace, Display};
6use crate::tree::{Layout, LayoutTree, NodeId, RunMode, SizeBaselinesAndMargins, SizingMode, Taffy, TaffyError};
7use crate::util::sys::round;
8
9#[cfg(feature = "block_layout")]
10use crate::compute::BlockAlgorithm;
11
12#[cfg(feature = "flexbox")]
13use crate::compute::FlexboxAlgorithm;
14
15#[cfg(feature = "grid")]
16use crate::compute::CssGridAlgorithm;
17
18#[cfg(any(feature = "debug", feature = "profile"))]
19use crate::util::debug::NODE_LOGGER;
20
21#[cfg(feature = "debug")]
22fn debug_log_node(
23    known_dimensions: Size<Option<f32>>,
24    parent_size: Size<Option<f32>>,
25    available_space: Size<AvailableSpace>,
26    run_mode: RunMode,
27    sizing_mode: SizingMode,
28) {
29    NODE_LOGGER.debug_log(run_mode);
30    NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
31    NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
32    NODE_LOGGER.labelled_debug_log("parent_size", parent_size);
33    NODE_LOGGER.labelled_debug_log("available_space", available_space);
34}
35
36/// Updates the stored layout of the provided `node` and its children
37pub(crate) fn compute_layout(
38    taffy: &mut Taffy,
39    root: NodeId,
40    available_space: Size<AvailableSpace>,
41) -> Result<(), TaffyError> {
42    taffy.is_layouting = true;
43
44    // Recursively compute node layout
45    let size_and_baselines = perform_node_layout(
46        taffy,
47        root,
48        Size::NONE,
49        available_space.into_options(),
50        available_space,
51        SizingMode::InherentSize,
52        Line::FALSE,
53    );
54
55    let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
56    *taffy.layout_mut(root) = layout;
57
58    // If rounding is enabled, recursively round the layout's of this node and all children
59    if taffy.config.use_rounding {
60        round_layout(taffy, root, 0.0, 0.0);
61    }
62
63    taffy.is_layouting = false;
64
65    Ok(())
66}
67
68/// Perform full layout on a node. Chooses which algorithm to use based on the `display` property.
69pub(crate) fn perform_node_layout(
70    tree: &mut Taffy,
71    node: NodeId,
72    known_dimensions: Size<Option<f32>>,
73    parent_size: Size<Option<f32>>,
74    available_space: Size<AvailableSpace>,
75    sizing_mode: SizingMode,
76    vertical_margins_are_collapsible: Line<bool>,
77) -> SizeBaselinesAndMargins {
78    compute_node_layout(
79        tree,
80        node,
81        known_dimensions,
82        parent_size,
83        available_space,
84        RunMode::PerformLayout,
85        sizing_mode,
86        vertical_margins_are_collapsible,
87    )
88}
89
90/// Measure a node's size. Chooses which algorithm to use based on the `display` property.
91pub(crate) fn measure_node_size(
92    tree: &mut Taffy,
93    node: NodeId,
94    known_dimensions: Size<Option<f32>>,
95    parent_size: Size<Option<f32>>,
96    available_space: Size<AvailableSpace>,
97    sizing_mode: SizingMode,
98    vertical_margins_are_collapsible: Line<bool>,
99) -> Size<f32> {
100    compute_node_layout(
101        tree,
102        node,
103        known_dimensions,
104        parent_size,
105        available_space,
106        RunMode::ComputeSize,
107        sizing_mode,
108        vertical_margins_are_collapsible,
109    )
110    .size
111}
112
113/// Updates the stored layout of the provided `node` and its children
114#[allow(clippy::too_many_arguments)]
115fn compute_node_layout(
116    tree: &mut Taffy,
117    node: NodeId,
118    known_dimensions: Size<Option<f32>>,
119    parent_size: Size<Option<f32>>,
120    available_space: Size<AvailableSpace>,
121    run_mode: RunMode,
122    sizing_mode: SizingMode,
123    vertical_margins_are_collapsible: Line<bool>,
124) -> SizeBaselinesAndMargins {
125    #[cfg(any(feature = "debug", feature = "profile"))]
126    NODE_LOGGER.push_node(node);
127    #[cfg(feature = "debug")]
128    println!();
129
130    let node_key = node.into();
131    let has_children = !tree.children[node_key].is_empty();
132
133    // First we check if we have a cached result for the given input
134    let cache_run_mode = if !has_children { RunMode::PerformLayout } else { run_mode };
135    if let Some(cached_size_and_baselines) =
136        tree.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode)
137    {
138        #[cfg(feature = "debug")]
139        NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
140        #[cfg(feature = "debug")]
141        debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
142        #[cfg(any(feature = "debug", feature = "profile"))]
143        NODE_LOGGER.pop_node();
144
145        return cached_size_and_baselines;
146    }
147
148    #[cfg(feature = "debug")]
149    debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
150
151    /// Inlined function generic over the LayoutAlgorithm to reduce code duplication
152    #[inline(always)]
153    fn perform_computations<Algorithm: LayoutAlgorithm>(
154        tree: &mut impl LayoutTree,
155        node: NodeId,
156        known_dimensions: Size<Option<f32>>,
157        parent_size: Size<Option<f32>>,
158        available_space: Size<AvailableSpace>,
159        run_mode: RunMode,
160        sizing_mode: SizingMode,
161        vertical_margins_are_collapsible: Line<bool>,
162    ) -> SizeBaselinesAndMargins {
163        #[cfg(feature = "debug")]
164        NODE_LOGGER.log(Algorithm::NAME);
165
166        match run_mode {
167            RunMode::PerformLayout => Algorithm::perform_layout(
168                tree,
169                node,
170                known_dimensions,
171                parent_size,
172                available_space,
173                sizing_mode,
174                vertical_margins_are_collapsible,
175            ),
176            RunMode::ComputeSize => Algorithm::measure_size(
177                tree,
178                node,
179                known_dimensions,
180                parent_size,
181                available_space,
182                sizing_mode,
183                vertical_margins_are_collapsible,
184            )
185            .into(),
186        }
187    }
188
189    let display_mode = tree.nodes[node_key].style.display;
190    let computed_size_and_baselines = match (display_mode, has_children) {
191        (Display::None, _) => {
192            perform_taffy_tree_hidden_layout(tree, node);
193            SizeBaselinesAndMargins::HIDDEN
194        }
195        #[cfg(feature = "block_layout")]
196        (Display::Block, true) => perform_computations::<BlockAlgorithm>(
197            tree,
198            node,
199            known_dimensions,
200            parent_size,
201            available_space,
202            run_mode,
203            sizing_mode,
204            vertical_margins_are_collapsible,
205        ),
206        #[cfg(feature = "flexbox")]
207        (Display::Flex, true) => perform_computations::<FlexboxAlgorithm>(
208            tree,
209            node,
210            known_dimensions,
211            parent_size,
212            available_space,
213            run_mode,
214            sizing_mode,
215            vertical_margins_are_collapsible,
216        ),
217        #[cfg(feature = "grid")]
218        (Display::Grid, true) => perform_computations::<CssGridAlgorithm>(
219            tree,
220            node,
221            known_dimensions,
222            parent_size,
223            available_space,
224            run_mode,
225            sizing_mode,
226            vertical_margins_are_collapsible,
227        ),
228        (_, false) => match run_mode {
229            RunMode::PerformLayout => leaf::perform_layout(
230                &tree.nodes[node_key].style,
231                tree.nodes[node_key].needs_measure.then(|| &tree.measure_funcs[node_key]),
232                known_dimensions,
233                parent_size,
234                available_space,
235                sizing_mode,
236                vertical_margins_are_collapsible,
237            ),
238            RunMode::ComputeSize => leaf::measure_size(
239                &tree.nodes[node_key].style,
240                tree.nodes[node_key].needs_measure.then(|| &tree.measure_funcs[node_key]),
241                known_dimensions,
242                parent_size,
243                available_space,
244                sizing_mode,
245                vertical_margins_are_collapsible,
246            )
247            .into(),
248        },
249    };
250
251    // Cache result
252    tree.nodes[node_key].cache.store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
253
254    #[cfg(feature = "debug")]
255    NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
256    #[cfg(any(feature = "debug", feature = "profile"))]
257    NODE_LOGGER.pop_node();
258
259    computed_size_and_baselines
260}
261
262/// Creates a layout for this node and its children, recursively.
263/// Each hidden node has zero size and is placed at the origin
264fn perform_taffy_tree_hidden_layout(tree: &mut Taffy, node: NodeId) {
265    /// Recursive function to apply hidden layout to all descendents
266    fn perform_hidden_layout_inner(tree: &mut Taffy, node: NodeId, order: u32) {
267        let node_key = node.into();
268        *tree.layout_mut(node) = Layout::with_order(order);
269        tree.nodes[node_key].cache.clear();
270        for order in 0..tree.children[node_key].len() {
271            perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
272        }
273    }
274
275    for order in 0..tree.children[node.into()].len() {
276        perform_hidden_layout_inner(tree, tree.child(node, order), order as _);
277    }
278}
279
280/// Rounds the calculated [`Layout`] to exact pixel values
281///
282/// In order to ensure that no gaps in the layout are introduced we:
283///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
284///     parent-relative coordinates
285///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
286///     rather than rounding the width/height directly
287/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
288///
289/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
290/// and write to `final_layout`.
291fn round_layout(tree: &mut Taffy, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
292    let node = &mut tree.nodes[node_id.into()];
293    let unrounded_layout = node.unrounded_layout;
294    let layout = &mut node.final_layout;
295
296    let cumulative_x = cumulative_x + unrounded_layout.location.x;
297    let cumulative_y = cumulative_y + unrounded_layout.location.y;
298
299    layout.location.x = round(unrounded_layout.location.x);
300    layout.location.y = round(unrounded_layout.location.y);
301    layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
302    layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
303
304    let child_count = tree.child_count(node_id).unwrap();
305    for index in 0..child_count {
306        let child = tree.child(node_id, index);
307        round_layout(tree, child, cumulative_x, cumulative_y);
308    }
309}