gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Sutichaykuna

⎇ daa07e0f4e3e009f5b0c11ada5df9785efd4b2c2 src/compute/taffy_tree.rs Rust · 165 siq'ikuna · 6.9 KB Ñawpa willay Pi imata llamkarqan ch'uya

Remove `LayoutAlgorithm` trait (#562) daa07e0f4 · Nico Burns ·

1//! Computation specific for the default `Taffy` tree implementation
2use crate::compute::leaf::compute_leaf_layout;
3use crate::geometry::{Line, Point, Size};
4use crate::style::{AvailableSpace, Display};
5use crate::tree::{
6    Layout, LayoutInput, LayoutOutput, LayoutTree, NodeId, RunMode, SizingMode, Taffy, TaffyError, TaffyView,
7};
8use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
9use crate::util::sys::round;
10
11#[cfg(feature = "block_layout")]
12use crate::compute::compute_block_layout;
13#[cfg(feature = "flexbox")]
14use crate::compute::compute_flexbox_layout;
15#[cfg(feature = "grid")]
16use crate::compute::compute_grid_layout;
17
18/// Updates the stored layout of the provided `node` and its children
19pub(crate) fn compute_layout<NodeContext, MeasureFunction>(
20    taffy_view: &mut TaffyView<NodeContext, MeasureFunction>,
21    root: NodeId,
22    available_space: Size<AvailableSpace>,
23) -> Result<(), TaffyError>
24where
25    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
26{
27    // Recursively compute node layout
28    let size_and_baselines = compute_node_layout(
29        taffy_view,
30        root,
31        LayoutInput {
32            known_dimensions: Size::NONE,
33            parent_size: available_space.into_options(),
34            available_space,
35            sizing_mode: SizingMode::InherentSize,
36            run_mode: RunMode::PerformLayout,
37            vertical_margins_are_collapsible: Line::FALSE,
38        },
39    );
40
41    let layout = Layout { order: 0, size: size_and_baselines.size, location: Point::ZERO };
42    *taffy_view.layout_mut(root) = layout;
43
44    // If rounding is enabled, recursively round the layout's of this node and all children
45    if taffy_view.taffy.config.use_rounding {
46        round_layout(taffy_view.taffy, root, 0.0, 0.0);
47    }
48
49    Ok(())
50}
51
52/// Updates the stored layout of the provided `node` and its children
53pub(crate) fn compute_node_layout<NodeContext, MeasureFunction>(
54    taffy_view: &mut TaffyView<NodeContext, MeasureFunction>,
55    node: NodeId,
56    inputs: LayoutInput,
57) -> LayoutOutput
58where
59    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
60{
61    debug_push_node!(node);
62
63    let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
64    let node_key = node.into();
65    let has_children = !taffy_view.taffy.children[node_key].is_empty();
66
67    // First we check if we have a cached result for the given input
68    let cache_run_mode = if !has_children { RunMode::PerformLayout } else { run_mode };
69    let cache = &taffy_view.taffy.nodes[node_key].cache;
70    let cache_entry = cache.get(known_dimensions, available_space, cache_run_mode);
71    if let Some(cached_size_and_baselines) = cache_entry {
72        debug_log!("CACHE", dbg:cached_size_and_baselines.size);
73        debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
74        debug_pop_node!();
75        return cached_size_and_baselines;
76    }
77
78    // Fetch `Display` style (+ debug log)
79    let display_mode = taffy_view.taffy.nodes[node_key].style.display;
80    debug_log!(display_mode);
81    debug_log_node!(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
82
83    let computed_size_and_baselines = match (display_mode, has_children) {
84        (Display::None, _) => perform_taffy_tree_hidden_layout(taffy_view.taffy, node),
85        #[cfg(feature = "block_layout")]
86        (Display::Block, true) => compute_block_layout(taffy_view, node, inputs),
87        #[cfg(feature = "flexbox")]
88        (Display::Flex, true) => compute_flexbox_layout(taffy_view, node, inputs),
89        #[cfg(feature = "grid")]
90        (Display::Grid, true) => compute_grid_layout(taffy_view, node, inputs),
91        (_, false) => compute_leaf_layout(
92            &mut taffy_view.measure_function,
93            node,
94            inputs,
95            &taffy_view.taffy.nodes[node_key].style,
96            taffy_view.taffy.nodes[node_key].needs_measure.then(|| &mut taffy_view.taffy.node_context_data[node_key]),
97        ),
98    };
99
100    // Cache result
101    taffy_view.taffy.nodes[node_key].cache.store(
102        known_dimensions,
103        available_space,
104        cache_run_mode,
105        computed_size_and_baselines,
106    );
107
108    debug_log!("RESULT", dbg:computed_size_and_baselines.size);
109    debug_pop_node!();
110
111    computed_size_and_baselines
112}
113
114/// Creates a layout for this node and its children, recursively.
115/// Each hidden node has zero size and is placed at the origin
116fn perform_taffy_tree_hidden_layout<NodeContext>(tree: &mut Taffy<NodeContext>, node: NodeId) -> LayoutOutput {
117    /// Recursive function to apply hidden layout to all descendents
118    fn perform_hidden_layout_inner<NodeContext>(tree: &mut Taffy<NodeContext>, node: NodeId, order: u32) {
119        let node_key = node.into();
120        tree.nodes[node_key].unrounded_layout = Layout::with_order(order);
121        tree.nodes[node_key].final_layout = Layout::with_order(order);
122        tree.nodes[node_key].cache.clear();
123        for order in 0..tree.children[node_key].len() {
124            perform_hidden_layout_inner(tree, tree.children[node_key][order], order as _);
125        }
126    }
127
128    let node_key = node.into();
129    for order in 0..tree.children[node.into()].len() {
130        perform_hidden_layout_inner(tree, tree.children[node_key][order], order as _);
131    }
132
133    LayoutOutput::HIDDEN
134}
135
136/// Rounds the calculated [`Layout`] to exact pixel values
137///
138/// In order to ensure that no gaps in the layout are introduced we:
139///   - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
140///     parent-relative coordinates
141///   - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
142///     rather than rounding the width/height directly
143/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
144///
145/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
146/// and write to `final_layout`.
147fn round_layout<NodeContext>(tree: &mut Taffy<NodeContext>, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
148    let node = &mut tree.nodes[node_id.into()];
149    let unrounded_layout = node.unrounded_layout;
150    let layout = &mut node.final_layout;
151
152    let cumulative_x = cumulative_x + unrounded_layout.location.x;
153    let cumulative_y = cumulative_y + unrounded_layout.location.y;
154
155    layout.location.x = round(unrounded_layout.location.x);
156    layout.location.y = round(unrounded_layout.location.y);
157    layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
158    layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
159
160    let child_count = tree.child_count(node_id).unwrap();
161    for index in 0..child_count {
162        let child = tree.children[node_id.into()][index];
163        round_layout(tree, child, cumulative_x, cumulative_y);
164    }
165}