taffy.rs

  1use crate::{
  2    AbsoluteLength, App, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, Window,
  3};
  4use collections::{FxHashMap, FxHashSet};
  5use smallvec::SmallVec;
  6use std::{fmt::Debug, ops::Range};
  7use taffy::{
  8    TaffyTree, TraversePartialTree as _,
  9    geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
 10    style::AvailableSpace as TaffyAvailableSpace,
 11    tree::NodeId,
 12};
 13
 14type NodeMeasureFn = Box<
 15    dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>,
 16>;
 17
 18struct NodeContext {
 19    measure: NodeMeasureFn,
 20}
 21pub struct TaffyLayoutEngine {
 22    taffy: TaffyTree<NodeContext>,
 23    absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
 24    computed_layouts: FxHashSet<LayoutId>,
 25}
 26
 27const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
 28
 29impl TaffyLayoutEngine {
 30    pub fn new() -> Self {
 31        let mut taffy = TaffyTree::new();
 32        taffy.disable_rounding();
 33        TaffyLayoutEngine {
 34            taffy,
 35            absolute_layout_bounds: FxHashMap::default(),
 36            computed_layouts: FxHashSet::default(),
 37        }
 38    }
 39
 40    pub fn clear(&mut self) {
 41        self.taffy.clear();
 42        self.absolute_layout_bounds.clear();
 43        self.computed_layouts.clear();
 44    }
 45
 46    pub fn request_layout(
 47        &mut self,
 48        style: Style,
 49        rem_size: Pixels,
 50        children: &[LayoutId],
 51    ) -> LayoutId {
 52        let taffy_style = style.to_taffy(rem_size);
 53        let layout_id = if children.is_empty() {
 54            self.taffy
 55                .new_leaf(taffy_style)
 56                .expect(EXPECT_MESSAGE)
 57                .into()
 58        } else {
 59            let parent_id = self
 60                .taffy
 61                // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
 62                .new_with_children(taffy_style, unsafe {
 63                    std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(children)
 64                })
 65                .expect(EXPECT_MESSAGE)
 66                .into();
 67            parent_id
 68        };
 69        layout_id
 70    }
 71
 72    pub fn request_measured_layout(
 73        &mut self,
 74        style: Style,
 75        rem_size: Pixels,
 76        measure: impl FnMut(
 77            Size<Option<Pixels>>,
 78            Size<AvailableSpace>,
 79            &mut Window,
 80            &mut App,
 81        ) -> Size<Pixels>
 82        + 'static,
 83    ) -> LayoutId {
 84        let taffy_style = style.to_taffy(rem_size);
 85
 86        let layout_id = self
 87            .taffy
 88            .new_leaf_with_context(
 89                taffy_style,
 90                NodeContext {
 91                    measure: Box::new(measure),
 92                },
 93            )
 94            .expect(EXPECT_MESSAGE)
 95            .into();
 96        layout_id
 97    }
 98
 99    // Used to understand performance
100    #[allow(dead_code)]
101    fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
102        let mut count = 0;
103
104        for child in self.taffy.children(parent.0)? {
105            // Count this child.
106            count += 1;
107
108            // Count all of this child's children.
109            count += self.count_all_children(LayoutId(child))?
110        }
111
112        Ok(count)
113    }
114
115    // Used to understand performance
116    #[allow(dead_code)]
117    fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
118        println!(
119            "{parent:?} at depth {depth} has {} children",
120            self.taffy.child_count(parent.0)
121        );
122
123        let mut max_child_depth = 0;
124
125        for child in self.taffy.children(parent.0)? {
126            max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
127        }
128
129        Ok(depth + 1 + max_child_depth)
130    }
131
132    // Used to understand performance
133    #[allow(dead_code)]
134    fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
135        let mut edges = Vec::new();
136
137        for child in self.taffy.children(parent.0)? {
138            edges.push((parent, LayoutId(child)));
139
140            edges.extend(self.get_edges(LayoutId(child))?);
141        }
142
143        Ok(edges)
144    }
145
146    pub fn compute_layout(
147        &mut self,
148        id: LayoutId,
149        available_space: Size<AvailableSpace>,
150        window: &mut Window,
151        cx: &mut App,
152    ) {
153        // Leaving this here until we have a better instrumentation approach.
154        // println!("Laying out {} children", self.count_all_children(id)?);
155        // println!("Max layout depth: {}", self.max_depth(0, id)?);
156
157        // Output the edges (branches) of the tree in Mermaid format for visualization.
158        // println!("Edges:");
159        // for (a, b) in self.get_edges(id)? {
160        //     println!("N{} --> N{}", u64::from(a), u64::from(b));
161        // }
162        // println!("");
163        //
164
165        if !self.computed_layouts.insert(id) {
166            let mut stack = SmallVec::<[LayoutId; 64]>::new();
167            stack.push(id);
168            while let Some(id) = stack.pop() {
169                self.absolute_layout_bounds.remove(&id);
170                stack.extend(
171                    self.taffy
172                        .children(id.into())
173                        .expect(EXPECT_MESSAGE)
174                        .into_iter()
175                        .map(Into::into),
176                );
177            }
178        }
179
180        // let started_at = std::time::Instant::now();
181        self.taffy
182            .compute_layout_with_measure(
183                id.into(),
184                available_space.into(),
185                |known_dimensions, available_space, _id, node_context, _style| {
186                    let Some(node_context) = node_context else {
187                        return taffy::geometry::Size::default();
188                    };
189
190                    let known_dimensions = Size {
191                        width: known_dimensions.width.map(Pixels),
192                        height: known_dimensions.height.map(Pixels),
193                    };
194
195                    (node_context.measure)(known_dimensions, available_space.into(), window, cx)
196                        .into()
197                },
198            )
199            .expect(EXPECT_MESSAGE);
200
201        // println!("compute_layout took {:?}", started_at.elapsed());
202    }
203
204    pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds<Pixels> {
205        if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
206            return layout;
207        }
208
209        let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
210        let mut bounds = Bounds {
211            origin: layout.location.into(),
212            size: layout.size.into(),
213        };
214
215        if let Some(parent_id) = self.taffy.parent(id.0) {
216            let parent_bounds = self.layout_bounds(parent_id.into());
217            bounds.origin += parent_bounds.origin;
218        }
219        self.absolute_layout_bounds.insert(id, bounds);
220
221        bounds
222    }
223}
224
225/// A unique identifier for a layout node, generated when requesting a layout from Taffy
226#[derive(Copy, Clone, Eq, PartialEq, Debug)]
227#[repr(transparent)]
228pub struct LayoutId(NodeId);
229
230impl std::hash::Hash for LayoutId {
231    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
232        u64::from(self.0).hash(state);
233    }
234}
235
236impl From<NodeId> for LayoutId {
237    fn from(node_id: NodeId) -> Self {
238        Self(node_id)
239    }
240}
241
242impl From<LayoutId> for NodeId {
243    fn from(layout_id: LayoutId) -> NodeId {
244        layout_id.0
245    }
246}
247
248trait ToTaffy<Output> {
249    fn to_taffy(&self, rem_size: Pixels) -> Output;
250}
251
252impl ToTaffy<taffy::style::Style> for Style {
253    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
254        use taffy::style_helpers::{fr, length, minmax, repeat};
255
256        fn to_grid_line(
257            placement: &Range<crate::GridPlacement>,
258        ) -> taffy::Line<taffy::GridPlacement> {
259            taffy::Line {
260                start: placement.start.into(),
261                end: placement.end.into(),
262            }
263        }
264
265        fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
266            unit: &Option<u16>,
267        ) -> Vec<taffy::GridTemplateComponent<T>> {
268            // grid-template-columns: repeat(<number>, minmax(0, 1fr));
269            unit.map(|count| vec![repeat(count, vec![minmax(length(0.0), fr(1.0))])])
270                .unwrap_or_default()
271        }
272
273        taffy::style::Style {
274            display: self.display.into(),
275            overflow: self.overflow.into(),
276            scrollbar_width: self.scrollbar_width,
277            position: self.position.into(),
278            inset: self.inset.to_taffy(rem_size),
279            size: self.size.to_taffy(rem_size),
280            min_size: self.min_size.to_taffy(rem_size),
281            max_size: self.max_size.to_taffy(rem_size),
282            aspect_ratio: self.aspect_ratio,
283            margin: self.margin.to_taffy(rem_size),
284            padding: self.padding.to_taffy(rem_size),
285            border: self.border_widths.to_taffy(rem_size),
286            align_items: self.align_items.map(|x| x.into()),
287            align_self: self.align_self.map(|x| x.into()),
288            align_content: self.align_content.map(|x| x.into()),
289            justify_content: self.justify_content.map(|x| x.into()),
290            gap: self.gap.to_taffy(rem_size),
291            flex_direction: self.flex_direction.into(),
292            flex_wrap: self.flex_wrap.into(),
293            flex_basis: self.flex_basis.to_taffy(rem_size),
294            flex_grow: self.flex_grow,
295            flex_shrink: self.flex_shrink,
296            grid_template_rows: to_grid_repeat(&self.grid_rows),
297            grid_template_columns: to_grid_repeat(&self.grid_cols),
298            grid_row: self
299                .grid_location
300                .as_ref()
301                .map(|location| to_grid_line(&location.row))
302                .unwrap_or_default(),
303            grid_column: self
304                .grid_location
305                .as_ref()
306                .map(|location| to_grid_line(&location.column))
307                .unwrap_or_default(),
308            ..Default::default()
309        }
310    }
311}
312
313impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
314    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
315        match self {
316            Length::Definite(length) => length.to_taffy(rem_size),
317            Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
318        }
319    }
320}
321
322impl ToTaffy<taffy::style::Dimension> for Length {
323    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
324        match self {
325            Length::Definite(length) => length.to_taffy(rem_size),
326            Length::Auto => taffy::prelude::Dimension::auto(),
327        }
328    }
329}
330
331impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
332    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
333        match self {
334            DefiniteLength::Absolute(length) => match length {
335                AbsoluteLength::Pixels(pixels) => {
336                    taffy::style::LengthPercentage::length(pixels.into())
337                }
338                AbsoluteLength::Rems(rems) => {
339                    taffy::style::LengthPercentage::length((*rems * rem_size).into())
340                }
341            },
342            DefiniteLength::Fraction(fraction) => {
343                taffy::style::LengthPercentage::percent(*fraction)
344            }
345        }
346    }
347}
348
349impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
350    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
351        match self {
352            DefiniteLength::Absolute(length) => match length {
353                AbsoluteLength::Pixels(pixels) => {
354                    taffy::style::LengthPercentageAuto::length(pixels.into())
355                }
356                AbsoluteLength::Rems(rems) => {
357                    taffy::style::LengthPercentageAuto::length((*rems * rem_size).into())
358                }
359            },
360            DefiniteLength::Fraction(fraction) => {
361                taffy::style::LengthPercentageAuto::percent(*fraction)
362            }
363        }
364    }
365}
366
367impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
368    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
369        match self {
370            DefiniteLength::Absolute(length) => match length {
371                AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::length(pixels.into()),
372                AbsoluteLength::Rems(rems) => {
373                    taffy::style::Dimension::length((*rems * rem_size).into())
374                }
375            },
376            DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
377        }
378    }
379}
380
381impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
382    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
383        match self {
384            AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::length(pixels.into()),
385            AbsoluteLength::Rems(rems) => {
386                taffy::style::LengthPercentage::length((*rems * rem_size).into())
387            }
388        }
389    }
390}
391
392impl<T, T2> From<TaffyPoint<T>> for Point<T2>
393where
394    T: Into<T2>,
395    T2: Clone + Debug + Default + PartialEq,
396{
397    fn from(point: TaffyPoint<T>) -> Point<T2> {
398        Point {
399            x: point.x.into(),
400            y: point.y.into(),
401        }
402    }
403}
404
405impl<T, T2> From<Point<T>> for TaffyPoint<T2>
406where
407    T: Into<T2> + Clone + Debug + Default + PartialEq,
408{
409    fn from(val: Point<T>) -> Self {
410        TaffyPoint {
411            x: val.x.into(),
412            y: val.y.into(),
413        }
414    }
415}
416
417impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
418where
419    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
420{
421    fn to_taffy(&self, rem_size: Pixels) -> TaffySize<U> {
422        TaffySize {
423            width: self.width.to_taffy(rem_size),
424            height: self.height.to_taffy(rem_size),
425        }
426    }
427}
428
429impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
430where
431    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
432{
433    fn to_taffy(&self, rem_size: Pixels) -> TaffyRect<U> {
434        TaffyRect {
435            top: self.top.to_taffy(rem_size),
436            right: self.right.to_taffy(rem_size),
437            bottom: self.bottom.to_taffy(rem_size),
438            left: self.left.to_taffy(rem_size),
439        }
440    }
441}
442
443impl<T, U> From<TaffySize<T>> for Size<U>
444where
445    T: Into<U>,
446    U: Clone + Debug + Default + PartialEq,
447{
448    fn from(taffy_size: TaffySize<T>) -> Self {
449        Size {
450            width: taffy_size.width.into(),
451            height: taffy_size.height.into(),
452        }
453    }
454}
455
456impl<T, U> From<Size<T>> for TaffySize<U>
457where
458    T: Into<U> + Clone + Debug + Default + PartialEq,
459{
460    fn from(size: Size<T>) -> Self {
461        TaffySize {
462            width: size.width.into(),
463            height: size.height.into(),
464        }
465    }
466}
467
468/// The space available for an element to be laid out in
469#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
470pub enum AvailableSpace {
471    /// The amount of space available is the specified number of pixels
472    Definite(Pixels),
473    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
474    #[default]
475    MinContent,
476    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
477    MaxContent,
478}
479
480impl AvailableSpace {
481    /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
482    ///
483    /// This function is useful when you want to create a `Size` with the minimum content constraints
484    /// for both dimensions.
485    ///
486    /// # Examples
487    ///
488    /// ```
489    /// let min_content_size = AvailableSpace::min_size();
490    /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
491    /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
492    /// ```
493    pub const fn min_size() -> Size<Self> {
494        Size {
495            width: Self::MinContent,
496            height: Self::MinContent,
497        }
498    }
499}
500
501impl From<AvailableSpace> for TaffyAvailableSpace {
502    fn from(space: AvailableSpace) -> TaffyAvailableSpace {
503        match space {
504            AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
505            AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
506            AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
507        }
508    }
509}
510
511impl From<TaffyAvailableSpace> for AvailableSpace {
512    fn from(space: TaffyAvailableSpace) -> AvailableSpace {
513        match space {
514            TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
515            TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
516            TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
517        }
518    }
519}
520
521impl From<Pixels> for AvailableSpace {
522    fn from(pixels: Pixels) -> Self {
523        AvailableSpace::Definite(pixels)
524    }
525}
526
527impl From<Size<Pixels>> for Size<AvailableSpace> {
528    fn from(size: Size<Pixels>) -> Self {
529        Size {
530            width: AvailableSpace::Definite(size.width),
531            height: AvailableSpace::Definite(size.height),
532        }
533    }
534}