Skip to main content

gpui_core/
taffy.rs

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