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::{cell::RefCell, 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
 14thread_local! {
 15    pub static LAYOUT_ID_TO_DEBUG: RefCell<Option<LayoutId>> = const { RefCell::new(None) };
 16}
 17
 18thread_local! {
 19    pub static CONTAINER_LAYOUT_ID_TO_DEBUG: RefCell<Option<LayoutId>> = const { RefCell::new(None) };
 20}
 21
 22type NodeMeasureFn = Box<
 23    dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>,
 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: 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    pub fn compute_layout(
155        &mut self,
156        id: LayoutId,
157        available_space: Size<AvailableSpace>,
158        window: &mut Window,
159        cx: &mut App,
160    ) {
161        // Leaving this here until we have a better instrumentation approach.
162        // println!("Laying out {} children", self.count_all_children(id)?);
163        // println!("Max layout depth: {}", self.max_depth(0, id)?);
164
165        // Output the edges (branches) of the tree in Mermaid format for visualization.
166        // println!("Edges:");
167        // for (a, b) in self.get_edges(id)? {
168        //     println!("N{} --> N{}", u64::from(a), u64::from(b));
169        // }
170        // println!("");
171        //
172
173        if !self.computed_layouts.insert(id) {
174            let mut stack = SmallVec::<[LayoutId; 64]>::new();
175            stack.push(id);
176            while let Some(id) = stack.pop() {
177                self.absolute_layout_bounds.remove(&id);
178                stack.extend(
179                    self.taffy
180                        .children(id.into())
181                        .expect(EXPECT_MESSAGE)
182                        .into_iter()
183                        .map(Into::into),
184                );
185            }
186        }
187
188        // let started_at = std::time::Instant::now();
189        self.taffy
190            .compute_layout_with_measure(
191                id.into(),
192                available_space.into(),
193                |known_dimensions, available_space, _id, node_context, _style| {
194                    let Some(node_context) = node_context else {
195                        return taffy::geometry::Size::default();
196                    };
197
198                    let known_dimensions = Size {
199                        width: known_dimensions.width.map(Pixels),
200                        height: known_dimensions.height.map(Pixels),
201                    };
202
203                    (node_context.measure)(known_dimensions, available_space.into(), window, cx)
204                        .into()
205                },
206            )
207            .expect(EXPECT_MESSAGE);
208
209        LAYOUT_ID_TO_DEBUG.with_borrow(|layout_id_to_debug| {
210            println!("Layout ID Debug: {:?}", layout_id_to_debug);
211        });
212
213        CONTAINER_LAYOUT_ID_TO_DEBUG.with_borrow(|layout_id| {
214            println!("Container Layout ID Debug: {:?}\n", layout_id);
215        });
216
217        // println!("compute_layout took {:?}", started_at.elapsed());
218    }
219
220    pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds<Pixels> {
221        if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
222            return layout;
223        }
224
225        let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
226        let mut bounds = Bounds {
227            origin: layout.location.into(),
228            size: layout.size.into(),
229        };
230
231        if let Some(parent_id) = self.taffy.parent(id.0) {
232            let parent_bounds = self.layout_bounds(parent_id.into());
233            bounds.origin += parent_bounds.origin;
234        }
235        self.absolute_layout_bounds.insert(id, bounds);
236
237        bounds
238    }
239}
240
241/// A unique identifier for a layout node, generated when requesting a layout from Taffy
242#[derive(Copy, Clone, Eq, PartialEq, Debug)]
243#[repr(transparent)]
244pub struct LayoutId(NodeId);
245
246impl std::hash::Hash for LayoutId {
247    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
248        u64::from(self.0).hash(state);
249    }
250}
251
252impl From<NodeId> for LayoutId {
253    fn from(node_id: NodeId) -> Self {
254        Self(node_id)
255    }
256}
257
258impl From<LayoutId> for NodeId {
259    fn from(layout_id: LayoutId) -> NodeId {
260        layout_id.0
261    }
262}
263
264trait ToTaffy<Output> {
265    fn to_taffy(&self, rem_size: Pixels) -> Output;
266}
267
268impl ToTaffy<taffy::style::Style> for Style {
269    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
270        use taffy::style_helpers::{fr, length, minmax, repeat};
271
272        fn to_grid_line(
273            placement: &Range<crate::GridPlacement>,
274        ) -> taffy::Line<taffy::GridPlacement> {
275            taffy::Line {
276                start: placement.start.into(),
277                end: placement.end.into(),
278            }
279        }
280
281        fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
282            unit: &Option<u16>,
283        ) -> Vec<taffy::GridTemplateComponent<T>> {
284            // grid-template-columns: repeat(<number>, minmax(0, 1fr));
285            unit.map(|count| vec![repeat(count, vec![minmax(length(0.0), fr(1.0))])])
286                .unwrap_or_default()
287        }
288
289        taffy::style::Style {
290            display: self.display.into(),
291            overflow: self.overflow.into(),
292            scrollbar_width: self.scrollbar_width,
293            position: self.position.into(),
294            inset: self.inset.to_taffy(rem_size),
295            size: self.size.to_taffy(rem_size),
296            min_size: self.min_size.to_taffy(rem_size),
297            max_size: self.max_size.to_taffy(rem_size),
298            aspect_ratio: self.aspect_ratio,
299            margin: self.margin.to_taffy(rem_size),
300            padding: self.padding.to_taffy(rem_size),
301            border: self.border_widths.to_taffy(rem_size),
302            align_items: self.align_items.map(|x| x.into()),
303            align_self: self.align_self.map(|x| x.into()),
304            align_content: self.align_content.map(|x| x.into()),
305            justify_content: self.justify_content.map(|x| x.into()),
306            gap: self.gap.to_taffy(rem_size),
307            flex_direction: self.flex_direction.into(),
308            flex_wrap: self.flex_wrap.into(),
309            flex_basis: self.flex_basis.to_taffy(rem_size),
310            flex_grow: self.flex_grow,
311            flex_shrink: self.flex_shrink,
312            grid_template_rows: to_grid_repeat(&self.grid_rows),
313            grid_template_columns: to_grid_repeat(&self.grid_cols),
314            grid_row: self
315                .grid_location
316                .as_ref()
317                .map(|location| to_grid_line(&location.row))
318                .unwrap_or_default(),
319            grid_column: self
320                .grid_location
321                .as_ref()
322                .map(|location| to_grid_line(&location.column))
323                .unwrap_or_default(),
324            ..Default::default()
325        }
326    }
327}
328
329impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
330    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
331        match self {
332            Length::Definite(length) => length.to_taffy(rem_size),
333            Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
334        }
335    }
336}
337
338impl ToTaffy<taffy::style::Dimension> for Length {
339    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
340        match self {
341            Length::Definite(length) => length.to_taffy(rem_size),
342            Length::Auto => taffy::prelude::Dimension::auto(),
343        }
344    }
345}
346
347impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
348    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
349        match self {
350            DefiniteLength::Absolute(length) => match length {
351                AbsoluteLength::Pixels(pixels) => {
352                    taffy::style::LengthPercentage::length(pixels.into())
353                }
354                AbsoluteLength::Rems(rems) => {
355                    taffy::style::LengthPercentage::length((*rems * rem_size).into())
356                }
357            },
358            DefiniteLength::Fraction(fraction) => {
359                taffy::style::LengthPercentage::percent(*fraction)
360            }
361        }
362    }
363}
364
365impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
366    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
367        match self {
368            DefiniteLength::Absolute(length) => match length {
369                AbsoluteLength::Pixels(pixels) => {
370                    taffy::style::LengthPercentageAuto::length(pixels.into())
371                }
372                AbsoluteLength::Rems(rems) => {
373                    taffy::style::LengthPercentageAuto::length((*rems * rem_size).into())
374                }
375            },
376            DefiniteLength::Fraction(fraction) => {
377                taffy::style::LengthPercentageAuto::percent(*fraction)
378            }
379        }
380    }
381}
382
383impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
384    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
385        match self {
386            DefiniteLength::Absolute(length) => match length {
387                AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::length(pixels.into()),
388                AbsoluteLength::Rems(rems) => {
389                    taffy::style::Dimension::length((*rems * rem_size).into())
390                }
391            },
392            DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
393        }
394    }
395}
396
397impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
398    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
399        match self {
400            AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::length(pixels.into()),
401            AbsoluteLength::Rems(rems) => {
402                taffy::style::LengthPercentage::length((*rems * rem_size).into())
403            }
404        }
405    }
406}
407
408impl<T, T2> From<TaffyPoint<T>> for Point<T2>
409where
410    T: Into<T2>,
411    T2: Clone + Debug + Default + PartialEq,
412{
413    fn from(point: TaffyPoint<T>) -> Point<T2> {
414        Point {
415            x: point.x.into(),
416            y: point.y.into(),
417        }
418    }
419}
420
421impl<T, T2> From<Point<T>> for TaffyPoint<T2>
422where
423    T: Into<T2> + Clone + Debug + Default + PartialEq,
424{
425    fn from(val: Point<T>) -> Self {
426        TaffyPoint {
427            x: val.x.into(),
428            y: val.y.into(),
429        }
430    }
431}
432
433impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
434where
435    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
436{
437    fn to_taffy(&self, rem_size: Pixels) -> TaffySize<U> {
438        TaffySize {
439            width: self.width.to_taffy(rem_size),
440            height: self.height.to_taffy(rem_size),
441        }
442    }
443}
444
445impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
446where
447    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
448{
449    fn to_taffy(&self, rem_size: Pixels) -> TaffyRect<U> {
450        TaffyRect {
451            top: self.top.to_taffy(rem_size),
452            right: self.right.to_taffy(rem_size),
453            bottom: self.bottom.to_taffy(rem_size),
454            left: self.left.to_taffy(rem_size),
455        }
456    }
457}
458
459impl<T, U> From<TaffySize<T>> for Size<U>
460where
461    T: Into<U>,
462    U: Clone + Debug + Default + PartialEq,
463{
464    fn from(taffy_size: TaffySize<T>) -> Self {
465        Size {
466            width: taffy_size.width.into(),
467            height: taffy_size.height.into(),
468        }
469    }
470}
471
472impl<T, U> From<Size<T>> for TaffySize<U>
473where
474    T: Into<U> + Clone + Debug + Default + PartialEq,
475{
476    fn from(size: Size<T>) -> Self {
477        TaffySize {
478            width: size.width.into(),
479            height: size.height.into(),
480        }
481    }
482}
483
484/// The space available for an element to be laid out in
485#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
486pub enum AvailableSpace {
487    /// The amount of space available is the specified number of pixels
488    Definite(Pixels),
489    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
490    #[default]
491    MinContent,
492    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
493    MaxContent,
494}
495
496impl AvailableSpace {
497    /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
498    ///
499    /// This function is useful when you want to create a `Size` with the minimum content constraints
500    /// for both dimensions.
501    ///
502    /// # Examples
503    ///
504    /// ```
505    /// let min_content_size = AvailableSpace::min_size();
506    /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
507    /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
508    /// ```
509    pub const fn min_size() -> Size<Self> {
510        Size {
511            width: Self::MinContent,
512            height: Self::MinContent,
513        }
514    }
515}
516
517impl From<AvailableSpace> for TaffyAvailableSpace {
518    fn from(space: AvailableSpace) -> TaffyAvailableSpace {
519        match space {
520            AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
521            AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
522            AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
523        }
524    }
525}
526
527impl From<TaffyAvailableSpace> for AvailableSpace {
528    fn from(space: TaffyAvailableSpace) -> AvailableSpace {
529        match space {
530            TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
531            TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
532            TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
533        }
534    }
535}
536
537impl From<Pixels> for AvailableSpace {
538    fn from(pixels: Pixels) -> Self {
539        AvailableSpace::Definite(pixels)
540    }
541}
542
543impl From<Size<Pixels>> for Size<AvailableSpace> {
544    fn from(size: Size<Pixels>) -> Self {
545        Size {
546            width: AvailableSpace::Definite(size.width),
547            height: AvailableSpace::Definite(size.height),
548        }
549    }
550}