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