Skip to main content

gpui/
taffy.rs

1use crate::{
2    AbsoluteLength, App, Bounds, DefiniteLength, Edges, GridTemplate, Length, Pixels, Point, Size,
3    Style, Window, 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 std::{fmt::Debug, ops::Range};
11use taffy::{
12    TaffyTree, TraversePartialTree as _,
13    geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
14    prelude::{max_content, min_content},
15    style::AvailableSpace as TaffyAvailableSpace,
16    tree::NodeId,
17};
18
19#[cfg(feature = "stacker")]
20type StackSafe<T> = stacksafe::StackSafe<T>;
21#[cfg(not(feature = "stacker"))]
22type StackSafe<T> = T;
23
24type MeasureFn =
25    dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>;
26type NodeMeasureFn = StackSafe<Box<MeasureFn>>;
27
28struct NodeContext {
29    measure: NodeMeasureFn,
30}
31pub struct TaffyLayoutEngine {
32    taffy: TaffyTree<NodeContext>,
33    absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
34    /// Unrounded absolute border-box top-left per-node coordinate in device pixels.
35    absolute_outer_origins: FxHashMap<LayoutId, Point<f32>>,
36    computed_layouts: FxHashSet<LayoutId>,
37    layout_bounds_scratch_space: Vec<LayoutId>,
38}
39
40const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
41
42impl TaffyLayoutEngine {
43    pub fn new() -> Self {
44        let mut taffy = TaffyTree::new();
45        taffy.disable_rounding();
46        TaffyLayoutEngine {
47            taffy,
48            absolute_layout_bounds: FxHashMap::default(),
49            absolute_outer_origins: FxHashMap::default(),
50            computed_layouts: FxHashSet::default(),
51            layout_bounds_scratch_space: Vec::new(),
52        }
53    }
54
55    pub fn clear(&mut self) {
56        self.taffy.clear();
57        self.absolute_layout_bounds.clear();
58        self.absolute_outer_origins.clear();
59        self.computed_layouts.clear();
60    }
61
62    pub fn request_layout(
63        &mut self,
64        style: Style,
65        rem_size: Pixels,
66        scale_factor: f32,
67        children: &[LayoutId],
68    ) -> LayoutId {
69        let taffy_style = style.to_taffy(rem_size, scale_factor);
70
71        if children.is_empty() {
72            self.taffy
73                .new_leaf(taffy_style)
74                .expect(EXPECT_MESSAGE)
75                .into()
76        } else {
77            self.taffy
78                // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
79                .new_with_children(taffy_style, LayoutId::to_taffy_slice(children))
80                .expect(EXPECT_MESSAGE)
81                .into()
82        }
83    }
84
85    pub fn request_measured_layout(
86        &mut self,
87        style: Style,
88        rem_size: Pixels,
89        scale_factor: f32,
90        measure: impl FnMut(
91            Size<Option<Pixels>>,
92            Size<AvailableSpace>,
93            &mut Window,
94            &mut App,
95        ) -> Size<Pixels>
96        + 'static,
97    ) -> LayoutId {
98        let taffy_style = style.to_taffy(rem_size, scale_factor);
99        let measure = Box::new(measure) as Box<MeasureFn>;
100        #[cfg(feature = "stacker")]
101        let measure = StackSafe::new(measure);
102
103        self.taffy
104            .new_leaf_with_context(taffy_style, NodeContext { measure })
105            .expect(EXPECT_MESSAGE)
106            .into()
107    }
108
109    /// Treats any `auto` dimension of the given node's style as filling `size`.
110    ///
111    /// This is applied to window roots before layout so they behave like the
112    /// root element on the web, which stretches to fill the initial containing
113    /// block (the viewport) unless given an explicit size. Explicitly styled
114    /// dimensions are preserved.
115    pub fn stretch_auto_size_to_fill(
116        &mut self,
117        id: LayoutId,
118        size: Size<Pixels>,
119        scale_factor: f32,
120    ) {
121        let style = self.taffy.style(id.0).expect(EXPECT_MESSAGE);
122        let stretch_width = style.size.width.is_auto();
123        let stretch_height = style.size.height.is_auto();
124        if !stretch_width && !stretch_height {
125            return;
126        }
127        let mut style = style.clone();
128        if stretch_width {
129            style.size.width =
130                taffy::style::Dimension::length(round_to_device_pixel(size.width.0, scale_factor));
131        }
132        if stretch_height {
133            style.size.height =
134                taffy::style::Dimension::length(round_to_device_pixel(size.height.0, scale_factor));
135        }
136        self.taffy.set_style(id.0, style).expect(EXPECT_MESSAGE);
137    }
138
139    // Used to understand performance
140    #[allow(dead_code)]
141    fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
142        let mut count = 0;
143
144        for child in self.taffy.children(parent.0)? {
145            // Count this child.
146            count += 1;
147
148            // Count all of this child's children.
149            count += self.count_all_children(LayoutId(child))?
150        }
151
152        Ok(count)
153    }
154
155    // Used to understand performance
156    #[allow(dead_code)]
157    fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
158        println!(
159            "{parent:?} at depth {depth} has {} children",
160            self.taffy.child_count(parent.0)
161        );
162
163        let mut max_child_depth = 0;
164
165        for child in self.taffy.children(parent.0)? {
166            max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
167        }
168
169        Ok(depth + 1 + max_child_depth)
170    }
171
172    // Used to understand performance
173    #[allow(dead_code)]
174    fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
175        let mut edges = Vec::new();
176
177        for child in self.taffy.children(parent.0)? {
178            edges.push((parent, LayoutId(child)));
179
180            edges.extend(self.get_edges(LayoutId(child))?);
181        }
182
183        Ok(edges)
184    }
185
186    #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
187    pub fn compute_layout(
188        &mut self,
189        id: LayoutId,
190        available_space: Size<AvailableSpace>,
191        window: &mut Window,
192        cx: &mut App,
193    ) {
194        // Leaving this here until we have a better instrumentation approach.
195        // println!("Laying out {} children", self.count_all_children(id)?);
196        // println!("Max layout depth: {}", self.max_depth(0, id)?);
197
198        // Output the edges (branches) of the tree in Mermaid format for visualization.
199        // println!("Edges:");
200        // for (a, b) in self.get_edges(id)? {
201        //     println!("N{} --> N{}", u64::from(a), u64::from(b));
202        // }
203        //
204
205        if !self.computed_layouts.insert(id) {
206            let stack = &mut self.layout_bounds_scratch_space;
207            stack.push(id);
208            while let Some(id) = stack.pop() {
209                self.absolute_layout_bounds.remove(&id);
210                self.absolute_outer_origins.remove(&id);
211                stack.extend(
212                    self.taffy
213                        .children(id.into())
214                        .expect(EXPECT_MESSAGE)
215                        .into_iter()
216                        .map(LayoutId::from),
217                );
218            }
219        }
220
221        let scale_factor = window.scale_factor();
222
223        let transform = |v: AvailableSpace| match v {
224            AvailableSpace::Definite(pixels) => {
225                AvailableSpace::Definite(Pixels(pixels.0 * scale_factor))
226            }
227            AvailableSpace::MinContent => AvailableSpace::MinContent,
228            AvailableSpace::MaxContent => AvailableSpace::MaxContent,
229        };
230        let available_space = size(
231            transform(available_space.width),
232            transform(available_space.height),
233        );
234
235        self.taffy
236            .compute_layout_with_measure(
237                id.into(),
238                available_space.into(),
239                |known_dimensions, available_space, _id, node_context, _style| {
240                    let Some(node_context) = node_context else {
241                        return taffy::geometry::Size::default();
242                    };
243
244                    let known_dimensions = Size {
245                        width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
246                        height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
247                    };
248
249                    let available_space: Size<AvailableSpace> = available_space.into();
250                    let untransform = |ev: AvailableSpace| match ev {
251                        AvailableSpace::Definite(pixels) => {
252                            AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
253                        }
254                        AvailableSpace::MinContent => AvailableSpace::MinContent,
255                        AvailableSpace::MaxContent => AvailableSpace::MaxContent,
256                    };
257                    let available_space = size(
258                        untransform(available_space.width),
259                        untransform(available_space.height),
260                    );
261
262                    let measured_size: Size<Pixels> =
263                        (node_context.measure)(known_dimensions, available_space, window, cx);
264                    snap_measured_size_to_device_pixels(measured_size, scale_factor).into()
265                },
266            )
267            .expect(EXPECT_MESSAGE);
268    }
269
270    // Pixel snapping
271    //
272    // Painting primitives at non-integer pixel coordinates produces blurry
273    // output. Pixel snapping converts layout coordinates into integer
274    // device-pixel coordinates so painted edges land exactly on physical
275    // pixel boundaries.
276    //
277    // Non-integer coordinates can arise for several reasons, including:
278    //   - flex distribution, percentages, centering, and text measurement
279    //     can produce fractional element sizes and positions;
280    //   - at fractional scale factors (for example 125% or 150%), integer
281    //     logical-pixel values can map to non-integer device-pixel values.
282    //
283    // We pixel-snap by rounding in device-pixel space, after multiplying
284    // by `scale_factor`, so that snapping targets physical pixels. Bounds
285    // are divided by `scale_factor` before being returned to GPUI.
286    //
287    // Midpoints are rounded toward zero. This is a stylistic choice: a
288    // 1-logical-pixel line at 150% scale should render as 1 dp rather than
289    // 2 dp.
290    //
291    // Pixel snapping is done in two phases:
292    //
293    //  1. Pre-layout metric snapping. Before Taffy computes layout, all
294    //     authored absolute lengths are rounded in `to_taffy`. This
295    //     includes borders, padding, gaps, and explicit sizes.
296    //     Custom-measured leaf nodes have their measured sizes rounded up
297    //     to integer device-pixel lengths.
298    //
299    //  2. Post-layout edge snapping. After Taffy resolves the tree, layout
300    //     relationships such as flex shares, grid tracks, percentages, and
301    //     centering can produce new fractional edge positions. Boxes now
302    //     have edges in absolute coordinates, and snapping must decide
303    //     where those edges land on the device-pixel grid.
304    //
305    // Ideally, post-layout snapping would satisfy:
306    //
307    //  - Edge closure. Two raw layout edges at the same absolute position
308    //    should snap to the same pixel column.
309    //  - Translation stability. A component's internal geometry should not
310    //    change when it moves to a new absolute position.
311    //
312    // These goals are in tension because rounding is not associative.
313    // The simple local schemes make different tradeoffs:
314    //
315    //  - Absolute edge rounding gives each window coordinate one answer,
316    //    so coincident edges always close globally. But a span's snapped
317    //    length is `round(far) - round(near)`, which may change by 1 dp
318    //    as its absolute origin moves.
319    //
320    //  - Parent-relative edge rounding rounds each child inside its
321    //    parent's coordinate space. This guarantees translation stability,
322    //    but a shared edge reached through different parents can
323    //    accumulate different rounding, causing non-closure between
324    //    cousins.
325    //
326    //  - Length rounding rounds each width, height, and thickness
327    //    independently and then places boxes from those rounded lengths.
328    //    Sizes stay stable under translation, but neighboring boxes derive
329    //    their shared boundary from different sources, so closure is not
330    //    guaranteed.
331    //
332    // We apply absolute edge rounding for each element's outer box in
333    // post-layout rounding to preserve closure. Border and padding widths
334    // are not touched by post-layout rounding; they keep their pre-layout
335    // rounded value so that they remain stable under translation.
336    //
337    // This gives both closure and translation stability in the case that
338    // all local metrics are integer device-pixel lengths. Pre-layout
339    // rounding covers that in most cases. The exception is metrics
340    // resolved by layout relationships, such as percentages. Outer box
341    // edges will still close globally, and painted border widths are still
342    // snapped independently, but the raw content-box origin can carry a
343    // 1dp residual into descendants.
344
345    pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
346        if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
347            return layout;
348        }
349
350        let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
351        let layout_location = layout.location;
352        let layout_size = layout.size;
353        let parent = self.taffy.parent(id.0);
354
355        let absolute_outer_origin = match parent {
356            Some(parent_id) => {
357                let parent_id = LayoutId::from(parent_id);
358                self.layout_bounds(parent_id, scale_factor);
359                let parent_origin = *self
360                    .absolute_outer_origins
361                    .get(&parent_id)
362                    .expect("parent absolute outer origin should be cached");
363                parent_origin + Point::from(layout_location)
364            }
365            None => Point::from(layout_location),
366        };
367        self.absolute_outer_origins
368            .insert(id, absolute_outer_origin);
369
370        let absolute_far = absolute_outer_origin + Point::from(Size::from(layout_size));
371        let snapped_bounds = Bounds::from_corners(
372            absolute_outer_origin.map(round_half_toward_zero),
373            absolute_far.map(round_half_toward_zero),
374        );
375
376        let bounds = (snapped_bounds / scale_factor).map(Pixels);
377        self.absolute_layout_bounds.insert(id, bounds);
378        bounds
379    }
380}
381
382/// A unique identifier for a layout node, generated when requesting a layout from Taffy
383#[derive(Copy, Clone, Eq, PartialEq, Debug)]
384#[repr(transparent)]
385pub struct LayoutId(NodeId);
386
387impl LayoutId {
388    fn to_taffy_slice(node_ids: &[Self]) -> &[taffy::NodeId] {
389        // SAFETY: LayoutId is repr(transparent) to taffy::tree::NodeId.
390        unsafe { std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(node_ids) }
391    }
392}
393
394impl std::hash::Hash for LayoutId {
395    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
396        u64::from(self.0).hash(state);
397    }
398}
399
400impl From<NodeId> for LayoutId {
401    fn from(node_id: NodeId) -> Self {
402        Self(node_id)
403    }
404}
405
406impl From<LayoutId> for NodeId {
407    fn from(layout_id: LayoutId) -> NodeId {
408        layout_id.0
409    }
410}
411
412fn snap_measured_size_to_device_pixels(size: Size<Pixels>, scale_factor: f32) -> Size<f32> {
413    size.map(|d| ceil_to_device_pixel(d.0.max(0.0), scale_factor))
414}
415
416fn border_widths_to_taffy(
417    widths: &Edges<AbsoluteLength>,
418    rem_size: Pixels,
419    scale_factor: f32,
420) -> TaffyRect<taffy::style::LengthPercentage> {
421    let snap = |w: &AbsoluteLength| {
422        taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
423            w.to_pixels(rem_size).0,
424            scale_factor,
425        ))
426    };
427    TaffyRect {
428        top: snap(&widths.top),
429        right: snap(&widths.right),
430        bottom: snap(&widths.bottom),
431        left: snap(&widths.left),
432    }
433}
434
435trait ToTaffy<Output> {
436    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
437}
438
439impl ToTaffy<taffy::style::Style> for Style {
440    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
441        use taffy::style_helpers::{fr, length, minmax, repeat};
442
443        fn to_grid_line(
444            placement: &Range<crate::GridPlacement>,
445        ) -> taffy::Line<taffy::GridPlacement> {
446            taffy::Line {
447                start: placement.start.into(),
448                end: placement.end.into(),
449            }
450        }
451
452        fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
453            unit: &Option<GridTemplate>,
454        ) -> Vec<taffy::GridTemplateComponent<T>> {
455            unit.map(|template| {
456                match template.min_size {
457                    // grid-template-*: repeat(<number>, minmax(0, 1fr));
458                    crate::GridTemplateMinSize::Zero => {
459                        vec![repeat(
460                            template.repeat,
461                            vec![minmax(length(0.0_f32), fr(1.0_f32))],
462                        )]
463                    }
464                    // grid-template-*: repeat(<number>, minmax(min-content, 1fr));
465                    crate::GridTemplateMinSize::MinContent => {
466                        vec![repeat(
467                            template.repeat,
468                            vec![minmax(min_content(), fr(1.0_f32))],
469                        )]
470                    }
471                    // grid-template-*: repeat(<number>, minmax(0, max-content))
472                    crate::GridTemplateMinSize::MaxContent => {
473                        vec![repeat(
474                            template.repeat,
475                            vec![minmax(length(0.0_f32), max_content())],
476                        )]
477                    }
478                }
479            })
480            .unwrap_or_default()
481        }
482
483        taffy::style::Style {
484            display: self.display.into(),
485            overflow: self.overflow.into(),
486            scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
487            position: self.position.into(),
488            inset: self.inset.to_taffy(rem_size, scale_factor),
489            size: self.size.to_taffy(rem_size, scale_factor),
490            min_size: self.min_size.to_taffy(rem_size, scale_factor),
491            max_size: self.max_size.to_taffy(rem_size, scale_factor),
492            aspect_ratio: self.aspect_ratio,
493            margin: self.margin.to_taffy(rem_size, scale_factor),
494            padding: self.padding.to_taffy(rem_size, scale_factor),
495            border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
496            align_items: self.align_items.map(|x| x.into()),
497            align_self: self.align_self.map(|x| x.into()),
498            align_content: self.align_content.map(|x| x.into()),
499            justify_content: self.justify_content.map(|x| x.into()),
500            gap: self.gap.to_taffy(rem_size, scale_factor),
501            flex_direction: self.flex_direction.into(),
502            flex_wrap: self.flex_wrap.into(),
503            flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
504            flex_grow: self.flex_grow,
505            flex_shrink: self.flex_shrink,
506            grid_template_rows: to_grid_repeat(&self.grid_rows),
507            grid_template_columns: to_grid_repeat(&self.grid_cols),
508            grid_row: self
509                .grid_location
510                .as_ref()
511                .map(|location| to_grid_line(&location.row))
512                .unwrap_or_default(),
513            grid_column: self
514                .grid_location
515                .as_ref()
516                .map(|location| to_grid_line(&location.column))
517                .unwrap_or_default(),
518            ..Default::default()
519        }
520    }
521}
522
523impl ToTaffy<f32> for AbsoluteLength {
524    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
525        round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
526    }
527}
528
529impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
530    fn to_taffy(
531        &self,
532        rem_size: Pixels,
533        scale_factor: f32,
534    ) -> taffy::prelude::LengthPercentageAuto {
535        match self {
536            Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
537            Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
538        }
539    }
540}
541
542impl ToTaffy<taffy::style::Dimension> for Length {
543    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
544        match self {
545            Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
546            Length::Auto => taffy::prelude::Dimension::auto(),
547        }
548    }
549}
550
551impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
552    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
553        match self {
554            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
555            DefiniteLength::Fraction(fraction) => {
556                taffy::style::LengthPercentage::percent(*fraction)
557            }
558        }
559    }
560}
561
562impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
563    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
564        match self {
565            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
566            DefiniteLength::Fraction(fraction) => {
567                taffy::style::LengthPercentageAuto::percent(*fraction)
568            }
569        }
570    }
571}
572
573impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
574    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
575        match self {
576            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
577            DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
578        }
579    }
580}
581
582impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
583    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
584        taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
585    }
586}
587
588impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
589    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
590        taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
591    }
592}
593
594impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
595    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
596        taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
597    }
598}
599
600impl<T, T2> From<TaffyPoint<T>> for Point<T2>
601where
602    T: Into<T2>,
603    T2: Clone + Debug + Default + PartialEq,
604{
605    fn from(point: TaffyPoint<T>) -> Point<T2> {
606        Point {
607            x: point.x.into(),
608            y: point.y.into(),
609        }
610    }
611}
612
613impl<T, T2> From<Point<T>> for TaffyPoint<T2>
614where
615    T: Into<T2> + Clone + Debug + Default + PartialEq,
616{
617    fn from(val: Point<T>) -> Self {
618        TaffyPoint {
619            x: val.x.into(),
620            y: val.y.into(),
621        }
622    }
623}
624
625impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
626where
627    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
628{
629    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
630        TaffySize {
631            width: self.width.to_taffy(rem_size, scale_factor),
632            height: self.height.to_taffy(rem_size, scale_factor),
633        }
634    }
635}
636
637impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
638where
639    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
640{
641    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
642        TaffyRect {
643            top: self.top.to_taffy(rem_size, scale_factor),
644            right: self.right.to_taffy(rem_size, scale_factor),
645            bottom: self.bottom.to_taffy(rem_size, scale_factor),
646            left: self.left.to_taffy(rem_size, scale_factor),
647        }
648    }
649}
650
651impl<T, U> From<TaffySize<T>> for Size<U>
652where
653    T: Into<U>,
654    U: Clone + Debug + Default + PartialEq,
655{
656    fn from(taffy_size: TaffySize<T>) -> Self {
657        Size {
658            width: taffy_size.width.into(),
659            height: taffy_size.height.into(),
660        }
661    }
662}
663
664impl<T, U> From<Size<T>> for TaffySize<U>
665where
666    T: Into<U> + Clone + Debug + Default + PartialEq,
667{
668    fn from(size: Size<T>) -> Self {
669        TaffySize {
670            width: size.width.into(),
671            height: size.height.into(),
672        }
673    }
674}
675
676/// The space available for an element to be laid out in
677#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
678pub enum AvailableSpace {
679    /// The amount of space available is the specified number of pixels
680    Definite(Pixels),
681    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
682    #[default]
683    MinContent,
684    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
685    MaxContent,
686}
687
688impl AvailableSpace {
689    /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
690    ///
691    /// This function is useful when you want to create a `Size` with the minimum content constraints
692    /// for both dimensions.
693    ///
694    /// # Examples
695    ///
696    /// ```
697    /// use gpui::AvailableSpace;
698    /// let min_content_size = AvailableSpace::min_size();
699    /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
700    /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
701    /// ```
702    pub const fn min_size() -> Size<Self> {
703        Size {
704            width: Self::MinContent,
705            height: Self::MinContent,
706        }
707    }
708}
709
710impl From<AvailableSpace> for TaffyAvailableSpace {
711    fn from(space: AvailableSpace) -> TaffyAvailableSpace {
712        match space {
713            AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
714            AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
715            AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
716        }
717    }
718}
719
720impl From<TaffyAvailableSpace> for AvailableSpace {
721    fn from(space: TaffyAvailableSpace) -> AvailableSpace {
722        match space {
723            TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
724            TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
725            TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
726        }
727    }
728}
729
730impl From<Pixels> for AvailableSpace {
731    fn from(pixels: Pixels) -> Self {
732        AvailableSpace::Definite(pixels)
733    }
734}
735
736impl From<Size<Pixels>> for Size<AvailableSpace> {
737    fn from(size: Size<Pixels>) -> Self {
738        Size {
739            width: AvailableSpace::Definite(size.width),
740            height: AvailableSpace::Definite(size.height),
741        }
742    }
743}
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748
749    #[test]
750    fn border_widths_to_taffy_use_stroke_snapping() {
751        let border_widths = Edges {
752            top: Pixels(0.0).into(),
753            right: Pixels(0.4).into(),
754            bottom: Pixels(0.5).into(),
755            left: Pixels(1.6).into(),
756        };
757        let taffy_border = border_widths_to_taffy(&border_widths, Pixels(16.0), 1.0);
758
759        assert_eq!(
760            taffy_border.top,
761            taffy::style::LengthPercentage::length(0.0)
762        );
763        assert_eq!(
764            taffy_border.right,
765            taffy::style::LengthPercentage::length(1.0)
766        );
767        assert_eq!(
768            taffy_border.bottom,
769            taffy::style::LengthPercentage::length(1.0)
770        );
771        assert_eq!(
772            taffy_border.left,
773            taffy::style::LengthPercentage::length(2.0)
774        );
775    }
776}