tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 709HTML 662Markdown 12TOML 62348 Definiciones

⎇ morphorm src/compute/taffy_tree.rs Rust · 346 líneas · 12.3 KB Historia Quién tocó qué crudo

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