taffy.rs

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