tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definitions

⎇ b570f0abd9ec730debdf8c71d48912fbee15dc42 src/compute/morphorm.rs Rust · 613 lines · 22.6 KB History Who touched what raw

Morphorm WIP b570f0abd · Nico Burns ·

1use crate::compute::LayoutAlgorithm;
2use crate::geometry::{Line, Point, Rect, Size};
3use crate::style::{
4    AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
5    LengthPercentageAuto, Position,
6};
7use crate::style::{FlexDirection, Style};
8use crate::tree::LayoutTree;
9use crate::tree::NodeId;
10use crate::util::{MaybeMath, MaybeResolve};
11use crate::tree::{Layout, RunMode, SizeBaselinesAndMargins, SizingMode};
12
13/// The public interface to Taffy's Flexbox algorithm implementation
14pub struct MorphormAlgorithm;
15impl LayoutAlgorithm for MorphormAlgorithm {
16    const NAME: &'static str = "MORPHORM";
17
18    fn perform_layout(
19        tree: &mut impl LayoutTree,
20        node: NodeId,
21        known_dimensions: Size<Option<f32>>,
22        parent_size: Size<Option<f32>>,
23        available_space: Size<AvailableSpace>,
24        _sizing_mode: SizingMode,
25        _vertical_margins_are_collapsible: Line<bool>,
26    ) -> SizeBaselinesAndMargins {
27        compute(tree, node, known_dimensions, parent_size, available_space, RunMode::PerformLayout)
28    }
29
30    fn measure_size(
31        tree: &mut impl LayoutTree,
32        node: NodeId,
33        known_dimensions: Size<Option<f32>>,
34        parent_size: Size<Option<f32>>,
35        available_space: Size<AvailableSpace>,
36        _sizing_mode: SizingMode,
37        _vertical_margins_are_collapsible: Line<bool>,
38    ) -> Size<f32> {
39        compute(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize).size
40    }
41}
42
43#[derive(Debug)]
44pub struct ChildNode {
45    // A reference to the node.
46    node: NodeId,
47    // Whether the child is in flow or not
48    is_in_flow: bool,
49    // The index of the node.
50    index: usize,
51
52    style: Style,
53
54
55    // // The stretch factor of the node.
56    // stretch_factor: Size<f32>,
57    // // The minimum constraint of the node.
58    // min: Size<Option<f32>>,
59    // // The maximum constraint of the node.
60    // max: Size<Option<f32>>,
61
62    // // Sum of the flex factors on the main axis of the node.
63    // main_flex_sum: f32,
64    // // The available free space on the main axis of the node.
65    // main_non_flex: f32,
66    // // A remainder used during stretch computation.
67    // main_remainder: f32,
68    // // Sum of the cross_before, cross, and cross_after flex factors of the node.
69    // cross_flex_sum: f32,
70
71    // cross_non_flex: f32,
72    // cross: f32,
73    // cross_remainder: f32,
74
75    // // Computed main-before space of the node.
76    // main_before: f32,
77    // // Computed main-after space of the node.
78    // main_after: f32,
79    // // Computed cross-before space of the node.
80    // cross_before: f32,
81    // // Computed cross-after space of the node.
82    // cross_after: f32,
83}
84
85/// Computes the layout of [`LayoutTree`] according to the flexbox algorithm
86pub fn compute(
87    tree: &mut impl LayoutTree,
88    node: NodeId,
89    known_dimensions: Size<Option<f32>>,
90    parent_size: Size<Option<f32>>,
91    available_space: Size<AvailableSpace>,
92    run_mode: RunMode,
93) -> SizeBaselinesAndMargins {
94
95    // TODO: Min/Max constraints for space and size
96
97    // The layout type of the node. Determines the main and cross axes of the children.
98    let style = tree.style(node).clone();
99    // let layout_type = style.flex_direction;
100    let is_row = style.flex_direction.is_row();
101
102    // // The desired main-axis and cross-axis sizes of the node.
103    // let main = node.main(store, parent_layout_type);
104    // let cross = node.cross(store, parent_layout_type);
105
106    // // Compute main-axis size.
107    // let mut computed_main = match main {
108    //     Pixels(val) => val,
109
110    //     Percentage(val) => (parent_main * (val / 100.0)).round(),
111
112    //     Stretch(_) => parent_main,
113
114    //     Auto => 0.0,
115    // };
116
117    let min_size = style.min_size.maybe_resolve(parent_size);
118    let max_size = style.max_size.maybe_resolve(parent_size);
119    let clamped_style_size =
120        style.size.maybe_resolve(parent_size).maybe_clamp(min_size, max_size);
121
122    // If both min and max in a given axis are set and max <= min then this determines the size in that axis
123    let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
124        (Some(min), Some(max)) if max <= min => Some(min),
125        _ => None,
126    });
127    let styled_based_known_dimensions = known_dimensions.or(min_max_definite_size).or(clamped_style_size);
128
129    // Short-circuit layout if the container's size is fully determined by the container's size and the run mode
130    // is ComputeSize (and thus the container's size is all that we're interested in)
131    if run_mode == RunMode::ComputeSize {
132        if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
133            return Size { width, height }.into();
134        }
135    }
136
137
138
139
140    let in_flow_children =  tree.children(node)
141        .map(|child| (child, tree.style(*child)))
142        .filter(|(_, style)| style.position != Position::Absolute)
143        .filter(|(_, style)| style.display != Display::None)
144        .enumerate()
145        .map(|(index, (child, child_style))| {
146
147          let child_min_size = child_style.min_size.maybe_resolve(parent_size);
148          let max_size = style.max_size.maybe_resolve(parent_size);
149          let clamped_style_size =
150              style.size.maybe_resolve(parent_size).maybe_clamp(min_size, max_size);
151
152          // If both min and max in a given axis are set and max <= min then this determines the size in that axis
153          let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
154              (Some(min), Some(max)) if max <= min => Some(min),
155              _ => None,
156          });
157          let styled_based_known_dimensions = min_max_definite_size.or(clamped_style_size);
158
159          let content_size = if child_style.size.width == Dimension::Auto || child_style.size.height == Dimension::Auto {
160            tree.measure_child_size(node, known_dimensions, parent_size, available_space, SizingMode::InherentSize, Line::NONE)
161          } else {
162            Size::zero()
163          };
164
165          ChildNode {
166            node,
167            is_in_flow: child_style.position != Position::Absolute
168            index,
169            style: child_style.clone(),
170          }
171        })
172        .collect();
173
174
175    // Sum of all non-flexible space and size on the main-axis of the node.
176    let mut main_non_flex = 0.0f32;
177
178    // Sum of all space and size flex factors on the main-axis of the node.
179    let mut main_flex_sum = 0.0;
180
181    // Sum of all child nodes on the main-axis.
182    let mut main_sum = 0.0f32;
183
184    // Maximum of all child nodes on the cross-axis.
185    let mut cross_max = 0.0f32;
186
187    // List of child nodes for the current node.
188    let mut children = SmallVec::<[ChildNode<N>; 3]>::new();
189
190    // List of stretch nodes for the current node.
191    // A stretch node is any flexible space/size. e.g. main_before, main, and main_after are separate stretch nodes
192    let mut stretch_nodes = SmallVec::<[StretchNode<N>; 3]>::new();
193
194    // Parent overrides for child auto space.
195    let node_child_main_before = node.child_main_before(store, layout_type);
196    let node_child_main_after = node.child_main_after(store, layout_type);
197    let node_child_cross_before = node.child_cross_before(store, layout_type);
198    let node_child_cross_after = node.child_cross_after(store, layout_type);
199    let node_child_main_between = node.main_between(store, layout_type);
200
201    // Determine index of first and last parent-directed child nodes.
202    let mut iter = node
203        .children(tree)
204        .enumerate()
205        .filter(|(_, child)| child.position_type(store).unwrap_or_default() == PositionType::ParentDirected);
206
207    let first = iter.next().map(|(index, _)| index);
208    let last = iter.last().map_or(first, |(index, _)| Some(index));
209
210    let num_children = node.children(tree).count();
211
212    // Compute non-flexible children.
213    for (index, child) in node.children(tree).enumerate() {
214        let child_position_type = child.position_type(store).unwrap_or_default();
215
216        let mut child_main_before = child.main_before(store, layout_type);
217        let child_main = child.main(store, layout_type);
218        let mut child_main_after = child.main_after(store, layout_type);
219
220        let mut child_cross_before = child.cross_before(store, layout_type);
221        let child_cross = child.cross(store, layout_type);
222        let mut child_cross_after = child.cross_after(store, layout_type);
223
224        // Apply parent overrides to auto child space.
225        if child_main_before == Units::Auto {
226            if first == Some(index) || child_position_type == PositionType::SelfDirected {
227                child_main_before = node_child_main_before;
228            } else {
229                child_main_before = node_child_main_between;
230            }
231        }
232
233        if child_main_after == Units::Auto && (last == Some(index) || child_position_type == PositionType::SelfDirected)
234        {
235            child_main_after = node_child_main_after;
236        }
237
238        if child_cross_before == Units::Auto {
239            child_cross_before = node_child_cross_before;
240        }
241
242        if child_cross_after == Units::Auto {
243            child_cross_after = node_child_cross_after;
244        }
245
246        // Sum of flex factors on the main and cross axes of the child node.
247        let mut child_main_flex_sum = 0.0;
248        let mut child_cross_flex_sum = 0.0;
249
250        let mut computed_child_main_before = 0.0;
251        let mut computed_child_main = 0.0;
252        let mut computed_child_main_after = 0.0;
253
254        let mut computed_child_cross_before = 0.0;
255        let mut computed_child_cross = 0.0;
256        let mut computed_child_cross_after = 0.0;
257
258        match child_cross_before {
259            Pixels(val) => {
260                computed_child_cross_before = val;
261            }
262
263            Percentage(val) => {
264                computed_child_cross_before = (parent_cross * (val / 100.0)).round();
265            }
266
267            Stretch(factor) => {
268                child_cross_flex_sum += factor;
269            }
270
271            _ => {}
272        }
273
274        let child_min_cross_before = child.min_cross_before(store, layout_type).to_px(parent_cross, DEFAULT_MIN);
275        let child_max_cross_before = child.max_cross_before(store, layout_type).to_px(parent_cross, DEFAULT_MAX);
276
277        computed_child_cross_before = computed_child_cross_before.clamp(child_min_cross_before, child_max_cross_before);
278
279        match child_cross_after {
280            Pixels(val) => {
281                computed_child_cross_after = val;
282            }
283
284            Percentage(val) => {
285                computed_child_cross_after = (parent_cross * (val / 100.0)).round();
286            }
287
288            Stretch(factor) => {
289                child_cross_flex_sum += factor;
290            }
291
292            _ => {}
293        }
294
295        let child_min_cross_after = child.min_cross_after(store, layout_type).to_px(parent_cross, DEFAULT_MIN);
296        let child_max_cross_after = child.max_cross_after(store, layout_type).to_px(parent_cross, DEFAULT_MAX);
297
298        computed_child_cross_after = computed_child_cross_after.clamp(child_min_cross_after, child_max_cross_after);
299
300        match child_cross {
301            Pixels(val) => {
302                computed_child_cross = val;
303            }
304
305            Percentage(val) => {
306                computed_child_cross = (parent_cross * (val / 100.0)).round();
307            }
308
309            Stretch(factor) => {
310                child_cross_flex_sum += factor;
311            }
312
313            _ => {}
314        }
315
316        let child_min_cross = child.min_cross(store, layout_type).to_px(parent_cross, DEFAULT_MIN);
317        let child_max_cross = child.max_cross(store, layout_type).to_px(parent_cross, DEFAULT_MAX);
318
319        computed_child_cross = computed_child_cross.clamp(child_min_cross, child_max_cross);
320
321        let child_min_main_before = child.min_main_before(store, layout_type).to_px(parent_main, DEFAULT_MIN);
322        let child_max_main_before = child.max_main_before(store, layout_type).to_px(parent_main, DEFAULT_MAX);
323
324        match child_main_before {
325            Pixels(val) => {
326                computed_child_main_before = val;
327            }
328
329            Percentage(val) => {
330                computed_child_main_before = (parent_main * (val / 100.0)).round();
331            }
332
333            Stretch(factor) => {
334                child_main_flex_sum += factor;
335
336                stretch_nodes.push(StretchNode {
337                    node: child,
338                    index,
339                    factor,
340                    min: child_min_main_before,
341                    max: child_max_main_before,
342                    axis: Axis::MainBefore,
343                });
344            }
345
346            _ => {}
347        }
348
349        computed_child_main_before = computed_child_main_before.clamp(child_min_main_before, child_max_main_before);
350
351        let child_min_main_after = child.min_main_after(store, layout_type).to_px(parent_main, DEFAULT_MIN);
352        let child_max_main_after = child.max_main_after(store, layout_type).to_px(parent_main, DEFAULT_MAX);
353
354        match child_main_after {
355            Pixels(val) => {
356                computed_child_main_after = val;
357            }
358
359            Percentage(val) => {
360                computed_child_main_after = (parent_main * (val / 100.0)).round();
361            }
362
363            Stretch(factor) => {
364                child_main_flex_sum += factor;
365
366                stretch_nodes.push(StretchNode {
367                    node: child,
368                    index,
369                    factor,
370                    min: child_min_main_after,
371                    max: child_max_main_after,
372                    axis: Axis::MainAfter,
373                });
374            }
375
376            _ => {}
377        }
378
379        computed_child_main_after = computed_child_main_after.clamp(child_min_main_after, child_max_main_after);
380
381        // Total computed size on the cross-axis of the child.
382        let mut child_cross_non_flex = computed_child_cross_before + computed_child_cross + computed_child_cross_after;
383
384        match child_main {
385            Stretch(factor) => {
386                child_main_flex_sum += factor;
387
388                stretch_nodes.push(StretchNode {
389                    node: child,
390                    index,
391                    factor,
392                    min: DEFAULT_MIN,
393                    max: DEFAULT_MAX,
394                    axis: Axis::Main,
395                });
396            }
397
398            _ => {
399                let child_size = layout(child, layout_type, parent_main, computed_child_cross, cache, tree, store);
400
401                computed_child_main = child_size.main;
402                computed_child_cross = child_size.cross;
403
404                if child_cross == Units::Auto {
405                    child_cross_non_flex += computed_child_cross;
406                }
407            }
408        }
409
410        // Total computed size on the main-axis of the child.
411        let child_main_non_flex = computed_child_main_before + computed_child_main + computed_child_main_after;
412
413        if child_position_type == PositionType::ParentDirected {
414            main_non_flex += child_main_non_flex;
415            main_flex_sum += child_main_flex_sum;
416
417            main_sum += child_main_non_flex;
418        } else {
419            main_sum = main_sum.max(child_main_non_flex);
420        }
421
422        cross_max = cross_max.max(child_cross_non_flex);
423
424        children.push(ChildNode {
425            node: child,
426            main_flex_sum: child_main_flex_sum,
427            main_non_flex: child_main_non_flex,
428            main_remainder: 0.0,
429            cross_flex_sum: child_cross_flex_sum,
430            cross_non_flex: child_cross_non_flex,
431            cross: computed_child_cross,
432            cross_remainder: 0.0,
433            main_before: computed_child_main_before,
434            main_after: computed_child_main_after,
435            cross_before: computed_child_cross_before,
436            cross_after: computed_child_cross_after,
437        });
438    }
439
440    // Calculate free space on the main-axis for the node.
441    let free_main_space = (parent_main.max(main_sum) - main_non_flex).max(0.0);
442    let mut remainder: f32 = 0.0;
443    let main_px_per_flex = free_main_space / main_flex_sum;
444
445    // Compute flexible space and size on the main axis.
446    for item in stretch_nodes.iter() {
447        let child_position_type = item.node.position_type(store).unwrap_or_default();
448
449        let actual_main = if child_position_type == PositionType::SelfDirected {
450            let child_main_free_space = (parent_main.max(main_sum) - children[item.index].main_non_flex).max(0.0);
451            let px_per_flex = child_main_free_space / children[item.index].main_flex_sum;
452            let desired_main = item.factor * px_per_flex + children[item.index].main_remainder;
453            let actual_main = desired_main.round();
454            children[item.index].main_remainder = desired_main - actual_main;
455            actual_main
456        } else {
457            let desired_main = item.factor * main_px_per_flex + remainder;
458            let actual_main = desired_main.round();
459            remainder = desired_main - actual_main;
460            actual_main
461        };
462
463        let computed_child_cross = children[item.index].cross;
464
465        match item.axis {
466            Axis::MainBefore => {
467                children[item.index].main_before = actual_main;
468            }
469
470            Axis::MainAfter => {
471                children[item.index].main_after = actual_main;
472            }
473
474            Axis::Main => {
475                let child_size = layout(item.node, layout_type, actual_main, computed_child_cross, cache, tree, store);
476
477                children[item.index].cross_non_flex += child_size.cross;
478                cross_max = cross_max.max(children[item.index].cross_non_flex);
479            }
480        }
481    }
482
483    // Compute flexible space and size on the cross-axis.
484    // This needs to be done after computing the main-axis because layout computation for stretch children may cause
485    // the cross space to change due to content size.
486    for child in children.iter_mut() {
487        let child_cross_free_space = parent_cross.max(cross_max) - child.cross_non_flex;
488        let cross_px_per_flex = child_cross_free_space / child.cross_flex_sum;
489
490        let child_cross_before = child.node.cross_before(store, layout_type);
491        let child_cross = child.node.cross(store, layout_type);
492        let child_cross_after = child.node.cross_after(store, layout_type);
493
494        if let Stretch(factor) = child_cross_before {
495            let desired_cross = factor * cross_px_per_flex + child.cross_remainder;
496            let actual_cross = desired_cross.round();
497            child.cross_remainder = desired_cross - actual_cross;
498            child.cross_before = actual_cross;
499        }
500
501        if let Stretch(factor) = child_cross {
502            let desired_cross = factor * cross_px_per_flex + child.cross_remainder;
503            let actual_cross = desired_cross.round();
504            child.cross_remainder = desired_cross - actual_cross;
505
506            // if cross == Units::Auto && num_children != 0 {
507                let size = layout(child.node, layout_type, parent_main, actual_cross, cache, tree, store);
508            //     println!("size: {:?}", size);
509            // } else {
510                // At this stage stretch nodes on the cross-axis can only be the determined size so we can set it directly
511                // in the cache without needing to call layout again.
512                // match layout_type {
513                //     LayoutType::Row => {
514                //         cache.set_height(child.node.key(), actual_cross);
515                //     }
516
517                //     LayoutType::Column => {
518                //         cache.set_width(child.node.key(), actual_cross);
519                //     }
520                // }
521            // }
522        }
523
524        if let Stretch(factor) = child_cross_after {
525            let desired_cross = factor * cross_px_per_flex + child.cross_remainder;
526            let actual_cross = desired_cross.round();
527            child.cross_remainder = desired_cross - actual_cross;
528            child.cross_after = actual_cross;
529        }
530    }
531
532    // Position children.
533    let mut main_pos = 0.0;
534    for child in children.iter() {
535        let child_position_type = child.node.position_type(store).unwrap_or_default();
536
537        match child_position_type {
538            PositionType::SelfDirected => match layout_type {
539                LayoutType::Row => {
540                    cache.set_posx(child.node.key(), child.main_before);
541                    cache.set_posy(child.node.key(), child.cross_before);
542                }
543
544                LayoutType::Column => {
545                    cache.set_posy(child.node.key(), child.main_before);
546                    cache.set_posx(child.node.key(), child.cross_before);
547                }
548            },
549
550            PositionType::ParentDirected => {
551                main_pos += child.main_before;
552
553                match layout_type {
554                    LayoutType::Row => {
555                        cache.set_posx(child.node.key(), main_pos);
556                        cache.set_posy(child.node.key(), child.cross_before);
557                        let child_width = cache.width(child.node.key());
558                        main_pos += child_width;
559                    }
560
561                    LayoutType::Column => {
562                        cache.set_posy(child.node.key(), main_pos);
563                        cache.set_posx(child.node.key(), child.cross_before);
564                        let child_height = cache.height(child.node.key());
565                        main_pos += child_height;
566                    }
567                }
568
569                main_pos += child.main_after;
570            }
571        };
572    }
573
574    // This part is required for auto size when the node has children but conflicts with the content size when the node doesn't have children
575    // TODO: Make it so a node can only have content size if it has no children?
576    // TODO: Potentially split and move this to before stretch calculations.
577    if num_children != 0 {
578        if parent_layout_type == layout_type {
579            if let Auto = main {
580                computed_main = main_sum;
581            }
582
583            if let Auto = cross {
584                computed_cross = cross_max;
585            }
586        } else {
587            if let Auto = main {
588                computed_main = cross_max;
589            }
590
591            if let Auto = cross {
592                computed_cross = main_sum;
593            }
594        }
595    }
596
597    // Set the computed size of the node in the cache.
598    match parent_layout_type {
599        LayoutType::Row => {
600            cache.set_width(node.key(), computed_main);
601            cache.set_height(node.key(), computed_cross);
602        }
603
604        LayoutType::Column => {
605            cache.set_height(node.key(), computed_main);
606            cache.set_width(node.key(), computed_cross);
607        }
608    }
609
610    // Return the computed size, propagating it back up the tree.
611    Size { main: computed_main, cross: computed_cross }.into()
612
613}