taffy.rs

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