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;
  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        taffy::style::Style {
255            display: self.display.into(),
256            overflow: self.overflow.into(),
257            scrollbar_width: self.scrollbar_width,
258            position: self.position.into(),
259            inset: self.inset.to_taffy(rem_size),
260            size: self.size.to_taffy(rem_size),
261            min_size: self.min_size.to_taffy(rem_size),
262            max_size: self.max_size.to_taffy(rem_size),
263            aspect_ratio: self.aspect_ratio,
264            margin: self.margin.to_taffy(rem_size),
265            padding: self.padding.to_taffy(rem_size),
266            border: self.border_widths.to_taffy(rem_size),
267            align_items: self.align_items.map(|x| x.into()),
268            align_self: self.align_self.map(|x| x.into()),
269            align_content: self.align_content.map(|x| x.into()),
270            justify_content: self.justify_content.map(|x| x.into()),
271            gap: self.gap.to_taffy(rem_size),
272            flex_direction: self.flex_direction.into(),
273            flex_wrap: self.flex_wrap.into(),
274            flex_basis: self.flex_basis.to_taffy(rem_size),
275            flex_grow: self.flex_grow,
276            flex_shrink: self.flex_shrink,
277            ..Default::default() // Ignore grid properties for now
278        }
279    }
280}
281
282impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
283    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
284        match self {
285            Length::Definite(length) => length.to_taffy(rem_size),
286            Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
287        }
288    }
289}
290
291impl ToTaffy<taffy::style::Dimension> for Length {
292    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
293        match self {
294            Length::Definite(length) => length.to_taffy(rem_size),
295            Length::Auto => taffy::prelude::Dimension::auto(),
296        }
297    }
298}
299
300impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
301    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
302        match self {
303            DefiniteLength::Absolute(length) => match length {
304                AbsoluteLength::Pixels(pixels) => {
305                    taffy::style::LengthPercentage::length(pixels.into())
306                }
307                AbsoluteLength::Rems(rems) => {
308                    taffy::style::LengthPercentage::length((*rems * rem_size).into())
309                }
310            },
311            DefiniteLength::Fraction(fraction) => {
312                taffy::style::LengthPercentage::percent(*fraction)
313            }
314        }
315    }
316}
317
318impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
319    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
320        match self {
321            DefiniteLength::Absolute(length) => match length {
322                AbsoluteLength::Pixels(pixels) => {
323                    taffy::style::LengthPercentageAuto::length(pixels.into())
324                }
325                AbsoluteLength::Rems(rems) => {
326                    taffy::style::LengthPercentageAuto::length((*rems * rem_size).into())
327                }
328            },
329            DefiniteLength::Fraction(fraction) => {
330                taffy::style::LengthPercentageAuto::percent(*fraction)
331            }
332        }
333    }
334}
335
336impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
337    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
338        match self {
339            DefiniteLength::Absolute(length) => match length {
340                AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::length(pixels.into()),
341                AbsoluteLength::Rems(rems) => {
342                    taffy::style::Dimension::length((*rems * rem_size).into())
343                }
344            },
345            DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
346        }
347    }
348}
349
350impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
351    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
352        match self {
353            AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::length(pixels.into()),
354            AbsoluteLength::Rems(rems) => {
355                taffy::style::LengthPercentage::length((*rems * rem_size).into())
356            }
357        }
358    }
359}
360
361impl<T, T2> From<TaffyPoint<T>> for Point<T2>
362where
363    T: Into<T2>,
364    T2: Clone + Debug + Default + PartialEq,
365{
366    fn from(point: TaffyPoint<T>) -> Point<T2> {
367        Point {
368            x: point.x.into(),
369            y: point.y.into(),
370        }
371    }
372}
373
374impl<T, T2> From<Point<T>> for TaffyPoint<T2>
375where
376    T: Into<T2> + Clone + Debug + Default + PartialEq,
377{
378    fn from(val: Point<T>) -> Self {
379        TaffyPoint {
380            x: val.x.into(),
381            y: val.y.into(),
382        }
383    }
384}
385
386impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
387where
388    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
389{
390    fn to_taffy(&self, rem_size: Pixels) -> TaffySize<U> {
391        TaffySize {
392            width: self.width.to_taffy(rem_size),
393            height: self.height.to_taffy(rem_size),
394        }
395    }
396}
397
398impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
399where
400    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
401{
402    fn to_taffy(&self, rem_size: Pixels) -> TaffyRect<U> {
403        TaffyRect {
404            top: self.top.to_taffy(rem_size),
405            right: self.right.to_taffy(rem_size),
406            bottom: self.bottom.to_taffy(rem_size),
407            left: self.left.to_taffy(rem_size),
408        }
409    }
410}
411
412impl<T, U> From<TaffySize<T>> for Size<U>
413where
414    T: Into<U>,
415    U: Clone + Debug + Default + PartialEq,
416{
417    fn from(taffy_size: TaffySize<T>) -> Self {
418        Size {
419            width: taffy_size.width.into(),
420            height: taffy_size.height.into(),
421        }
422    }
423}
424
425impl<T, U> From<Size<T>> for TaffySize<U>
426where
427    T: Into<U> + Clone + Debug + Default + PartialEq,
428{
429    fn from(size: Size<T>) -> Self {
430        TaffySize {
431            width: size.width.into(),
432            height: size.height.into(),
433        }
434    }
435}
436
437/// The space available for an element to be laid out in
438#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
439pub enum AvailableSpace {
440    /// The amount of space available is the specified number of pixels
441    Definite(Pixels),
442    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
443    #[default]
444    MinContent,
445    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
446    MaxContent,
447}
448
449impl AvailableSpace {
450    /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
451    ///
452    /// This function is useful when you want to create a `Size` with the minimum content constraints
453    /// for both dimensions.
454    ///
455    /// # Examples
456    ///
457    /// ```
458    /// let min_content_size = AvailableSpace::min_size();
459    /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
460    /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
461    /// ```
462    pub const fn min_size() -> Size<Self> {
463        Size {
464            width: Self::MinContent,
465            height: Self::MinContent,
466        }
467    }
468}
469
470impl From<AvailableSpace> for TaffyAvailableSpace {
471    fn from(space: AvailableSpace) -> TaffyAvailableSpace {
472        match space {
473            AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
474            AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
475            AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
476        }
477    }
478}
479
480impl From<TaffyAvailableSpace> for AvailableSpace {
481    fn from(space: TaffyAvailableSpace) -> AvailableSpace {
482        match space {
483            TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
484            TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
485            TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
486        }
487    }
488}
489
490impl From<Pixels> for AvailableSpace {
491    fn from(pixels: Pixels) -> Self {
492        AvailableSpace::Definite(pixels)
493    }
494}
495
496impl From<Size<Pixels>> for Size<AvailableSpace> {
497    fn from(size: Size<Pixels>) -> Self {
498        Size {
499            width: AvailableSpace::Definite(size.width),
500            height: AvailableSpace::Definite(size.height),
501        }
502    }
503}