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                |input, _id, node_context, style| {
240                    taffy::compute_leaf_layout(
241                        input,
242                        style,
243                        |_, _| 0.0,
244                        |known_dimensions, available_space| {
245                            let Some(node_context) = node_context else {
246                                return taffy::geometry::Size::default();
247                            };
248
249                            let known_dimensions = Size {
250                                width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
251                                height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
252                            };
253
254                            let available_space: Size<AvailableSpace> = available_space.into();
255                            let untransform = |ev: AvailableSpace| match ev {
256                                AvailableSpace::Definite(pixels) => {
257                                    AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
258                                }
259                                AvailableSpace::MinContent => AvailableSpace::MinContent,
260                                AvailableSpace::MaxContent => AvailableSpace::MaxContent,
261                            };
262                            let available_space = size(
263                                untransform(available_space.width),
264                                untransform(available_space.height),
265                            );
266
267                            let measured_size: Size<Pixels> = (node_context.measure)(
268                                known_dimensions,
269                                available_space,
270                                window,
271                                cx,
272                            );
273                            snap_measured_size_to_device_pixels(measured_size, scale_factor).into()
274                        },
275                    )
276                },
277            )
278            .expect(EXPECT_MESSAGE);
279    }
280
281    // Pixel snapping
282    //
283    // Painting primitives at non-integer pixel coordinates produces blurry
284    // output. Pixel snapping converts layout coordinates into integer
285    // device-pixel coordinates so painted edges land exactly on physical
286    // pixel boundaries.
287    //
288    // Non-integer coordinates can arise for several reasons, including:
289    //   - flex distribution, percentages, centering, and text measurement
290    //     can produce fractional element sizes and positions;
291    //   - at fractional scale factors (for example 125% or 150%), integer
292    //     logical-pixel values can map to non-integer device-pixel values.
293    //
294    // We pixel-snap by rounding in device-pixel space, after multiplying
295    // by `scale_factor`, so that snapping targets physical pixels. Bounds
296    // are divided by `scale_factor` before being returned to GPUI.
297    //
298    // Midpoints are rounded toward zero. This is a stylistic choice: a
299    // 1-logical-pixel line at 150% scale should render as 1 dp rather than
300    // 2 dp.
301    //
302    // Pixel snapping is done in two phases:
303    //
304    //  1. Pre-layout metric snapping. Before Taffy computes layout, all
305    //     authored absolute lengths are rounded in `to_taffy`. This
306    //     includes borders, padding, gaps, and explicit sizes.
307    //     Custom-measured leaf nodes have their measured sizes rounded up
308    //     to integer device-pixel lengths.
309    //
310    //  2. Post-layout edge snapping. After Taffy resolves the tree, layout
311    //     relationships such as flex shares, grid tracks, percentages, and
312    //     centering can produce new fractional edge positions. Boxes now
313    //     have edges in absolute coordinates, and snapping must decide
314    //     where those edges land on the device-pixel grid.
315    //
316    // Ideally, post-layout snapping would satisfy:
317    //
318    //  - Edge closure. Two raw layout edges at the same absolute position
319    //    should snap to the same pixel column.
320    //  - Translation stability. A component's internal geometry should not
321    //    change when it moves to a new absolute position.
322    //
323    // These goals are in tension because rounding is not associative.
324    // The simple local schemes make different tradeoffs:
325    //
326    //  - Absolute edge rounding gives each window coordinate one answer,
327    //    so coincident edges always close globally. But a span's snapped
328    //    length is `round(far) - round(near)`, which may change by 1 dp
329    //    as its absolute origin moves.
330    //
331    //  - Parent-relative edge rounding rounds each child inside its
332    //    parent's coordinate space. This guarantees translation stability,
333    //    but a shared edge reached through different parents can
334    //    accumulate different rounding, causing non-closure between
335    //    cousins.
336    //
337    //  - Length rounding rounds each width, height, and thickness
338    //    independently and then places boxes from those rounded lengths.
339    //    Sizes stay stable under translation, but neighboring boxes derive
340    //    their shared boundary from different sources, so closure is not
341    //    guaranteed.
342    //
343    // We apply absolute edge rounding for each element's outer box in
344    // post-layout rounding to preserve closure. Border and padding widths
345    // are not touched by post-layout rounding; they keep their pre-layout
346    // rounded value so that they remain stable under translation.
347    //
348    // This gives both closure and translation stability in the case that
349    // all local metrics are integer device-pixel lengths. Pre-layout
350    // rounding covers that in most cases. The exception is metrics
351    // resolved by layout relationships, such as percentages. Outer box
352    // edges will still close globally, and painted border widths are still
353    // snapped independently, but the raw content-box origin can carry a
354    // 1dp residual into descendants.
355
356    pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
357        if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
358            return layout;
359        }
360
361        let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
362        let layout_location = layout.location;
363        let layout_size = layout.size;
364        let parent = self.taffy.parent(id.0);
365
366        let absolute_outer_origin = match parent {
367            Some(parent_id) => {
368                let parent_id = LayoutId::from(parent_id);
369                self.layout_bounds(parent_id, scale_factor);
370                let parent_origin = *self
371                    .absolute_outer_origins
372                    .get(&parent_id)
373                    .expect("parent absolute outer origin should be cached");
374                parent_origin + Point::from(layout_location)
375            }
376            None => Point::from(layout_location),
377        };
378        self.absolute_outer_origins
379            .insert(id, absolute_outer_origin);
380
381        let absolute_far = absolute_outer_origin + Point::from(Size::from(layout_size));
382        let snapped_bounds = Bounds::from_corners(
383            absolute_outer_origin.map(round_half_toward_zero),
384            absolute_far.map(round_half_toward_zero),
385        );
386
387        let bounds = (snapped_bounds / scale_factor).map(Pixels);
388        self.absolute_layout_bounds.insert(id, bounds);
389        bounds
390    }
391}
392
393/// A unique identifier for a layout node, generated when requesting a layout from Taffy
394#[derive(Copy, Clone, Eq, PartialEq, Debug)]
395#[repr(transparent)]
396pub struct LayoutId(NodeId);
397
398impl LayoutId {
399    fn to_taffy_slice(node_ids: &[Self]) -> &[taffy::NodeId] {
400        // SAFETY: LayoutId is repr(transparent) to taffy::tree::NodeId.
401        unsafe { std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(node_ids) }
402    }
403}
404
405impl std::hash::Hash for LayoutId {
406    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
407        u64::from(self.0).hash(state);
408    }
409}
410
411impl From<NodeId> for LayoutId {
412    fn from(node_id: NodeId) -> Self {
413        Self(node_id)
414    }
415}
416
417impl From<LayoutId> for NodeId {
418    fn from(layout_id: LayoutId) -> NodeId {
419        layout_id.0
420    }
421}
422
423fn snap_measured_size_to_device_pixels(size: Size<Pixels>, scale_factor: f32) -> Size<f32> {
424    size.map(|d| ceil_to_device_pixel(d.0.max(0.0), scale_factor))
425}
426
427fn border_widths_to_taffy(
428    widths: &Edges<AbsoluteLength>,
429    rem_size: Pixels,
430    scale_factor: f32,
431) -> TaffyRect<taffy::style::LengthPercentage> {
432    let snap = |w: &AbsoluteLength| {
433        taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
434            w.to_pixels(rem_size).0,
435            scale_factor,
436        ))
437    };
438    TaffyRect {
439        top: snap(&widths.top),
440        right: snap(&widths.right),
441        bottom: snap(&widths.bottom),
442        left: snap(&widths.left),
443    }
444}
445
446trait ToTaffy<Output> {
447    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
448}
449
450impl ToTaffy<taffy::style::Style> for Style {
451    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
452        use taffy::style_helpers::{fr, length, minmax, repeat};
453
454        fn to_grid_line(
455            placement: &Range<crate::GridPlacement>,
456        ) -> taffy::Line<taffy::GridPlacement> {
457            taffy::Line {
458                start: placement.start.into(),
459                end: placement.end.into(),
460            }
461        }
462
463        fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
464            unit: &Option<GridTemplate>,
465        ) -> Vec<taffy::GridTemplateComponent<T>> {
466            unit.map(|template| {
467                match template.min_size {
468                    // grid-template-*: repeat(<number>, minmax(0, 1fr));
469                    crate::GridTemplateMinSize::Zero => {
470                        vec![repeat(
471                            template.repeat,
472                            vec![minmax(length(0.0_f32), fr(1.0_f32))],
473                        )]
474                    }
475                    // grid-template-*: repeat(<number>, minmax(min-content, 1fr));
476                    crate::GridTemplateMinSize::MinContent => {
477                        vec![repeat(
478                            template.repeat,
479                            vec![minmax(min_content(), fr(1.0_f32))],
480                        )]
481                    }
482                    // grid-template-*: repeat(<number>, minmax(0, max-content))
483                    crate::GridTemplateMinSize::MaxContent => {
484                        vec![repeat(
485                            template.repeat,
486                            vec![minmax(length(0.0_f32), max_content())],
487                        )]
488                    }
489                }
490            })
491            .unwrap_or_default()
492        }
493
494        taffy::style::Style {
495            display: self.display.into(),
496            overflow: self.overflow.into(),
497            scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
498            position: self.position.into(),
499            inset: self.inset.to_taffy(rem_size, scale_factor),
500            size: self.size.to_taffy(rem_size, scale_factor),
501            min_size: self.min_size.to_taffy(rem_size, scale_factor),
502            max_size: self.max_size.to_taffy(rem_size, scale_factor),
503            aspect_ratio: self.aspect_ratio,
504            margin: self.margin.to_taffy(rem_size, scale_factor),
505            padding: self.padding.to_taffy(rem_size, scale_factor),
506            border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
507            align_items: self.align_items.map(Into::into).unwrap_or_default(),
508            align_self: self.align_self.map(|x| x.into()),
509            align_content: self.align_content.map(Into::into).unwrap_or_default(),
510            justify_content: self.justify_content.map(Into::into).unwrap_or_default(),
511            gap: self.gap.to_taffy(rem_size, scale_factor),
512            flex_direction: self.flex_direction.into(),
513            flex_wrap: self.flex_wrap.into(),
514            flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
515            flex_grow: self.flex_grow,
516            flex_shrink: self.flex_shrink,
517            grid_template_rows: to_grid_repeat(&self.grid_rows),
518            grid_template_columns: to_grid_repeat(&self.grid_cols),
519            grid_row: self
520                .grid_location
521                .as_ref()
522                .map(|location| to_grid_line(&location.row))
523                .unwrap_or_default(),
524            grid_column: self
525                .grid_location
526                .as_ref()
527                .map(|location| to_grid_line(&location.column))
528                .unwrap_or_default(),
529            ..Default::default()
530        }
531    }
532}
533
534impl ToTaffy<f32> for AbsoluteLength {
535    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
536        round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
537    }
538}
539
540impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
541    fn to_taffy(
542        &self,
543        rem_size: Pixels,
544        scale_factor: f32,
545    ) -> taffy::prelude::LengthPercentageAuto {
546        match self {
547            Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
548            Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
549        }
550    }
551}
552
553impl ToTaffy<taffy::style::Dimension> for Length {
554    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
555        match self {
556            Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
557            Length::Auto => taffy::prelude::Dimension::auto(),
558        }
559    }
560}
561
562impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
563    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
564        match self {
565            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
566            DefiniteLength::Fraction(fraction) => {
567                taffy::style::LengthPercentage::percent(*fraction)
568            }
569        }
570    }
571}
572
573impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
574    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
575        match self {
576            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
577            DefiniteLength::Fraction(fraction) => {
578                taffy::style::LengthPercentageAuto::percent(*fraction)
579            }
580        }
581    }
582}
583
584impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
585    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
586        match self {
587            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
588            DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
589        }
590    }
591}
592
593impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
594    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
595        taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
596    }
597}
598
599impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
600    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
601        taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
602    }
603}
604
605impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
606    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
607        taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
608    }
609}
610
611impl<T, T2> From<TaffyPoint<T>> for Point<T2>
612where
613    T: Into<T2>,
614    T2: Clone + Debug + Default + PartialEq,
615{
616    fn from(point: TaffyPoint<T>) -> Point<T2> {
617        Point {
618            x: point.x.into(),
619            y: point.y.into(),
620        }
621    }
622}
623
624impl<T, T2> From<Point<T>> for TaffyPoint<T2>
625where
626    T: Into<T2> + Clone + Debug + Default + PartialEq,
627{
628    fn from(val: Point<T>) -> Self {
629        TaffyPoint {
630            x: val.x.into(),
631            y: val.y.into(),
632        }
633    }
634}
635
636impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
637where
638    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
639{
640    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
641        TaffySize {
642            width: self.width.to_taffy(rem_size, scale_factor),
643            height: self.height.to_taffy(rem_size, scale_factor),
644        }
645    }
646}
647
648impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
649where
650    T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
651{
652    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
653        TaffyRect {
654            top: self.top.to_taffy(rem_size, scale_factor),
655            right: self.right.to_taffy(rem_size, scale_factor),
656            bottom: self.bottom.to_taffy(rem_size, scale_factor),
657            left: self.left.to_taffy(rem_size, scale_factor),
658        }
659    }
660}
661
662impl<T, U> From<TaffySize<T>> for Size<U>
663where
664    T: Into<U>,
665    U: Clone + Debug + Default + PartialEq,
666{
667    fn from(taffy_size: TaffySize<T>) -> Self {
668        Size {
669            width: taffy_size.width.into(),
670            height: taffy_size.height.into(),
671        }
672    }
673}
674
675impl<T, U> From<Size<T>> for TaffySize<U>
676where
677    T: Into<U> + Clone + Debug + Default + PartialEq,
678{
679    fn from(size: Size<T>) -> Self {
680        TaffySize {
681            width: size.width.into(),
682            height: size.height.into(),
683        }
684    }
685}
686
687/// The space available for an element to be laid out in
688#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
689pub enum AvailableSpace {
690    /// The amount of space available is the specified number of pixels
691    Definite(Pixels),
692    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
693    #[default]
694    MinContent,
695    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
696    MaxContent,
697}
698
699impl AvailableSpace {
700    /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
701    ///
702    /// This function is useful when you want to create a `Size` with the minimum content constraints
703    /// for both dimensions.
704    ///
705    /// # Examples
706    ///
707    /// ```
708    /// use gpui::AvailableSpace;
709    /// let min_content_size = AvailableSpace::min_size();
710    /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
711    /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
712    /// ```
713    pub const fn min_size() -> Size<Self> {
714        Size {
715            width: Self::MinContent,
716            height: Self::MinContent,
717        }
718    }
719}
720
721impl From<AvailableSpace> for TaffyAvailableSpace {
722    fn from(space: AvailableSpace) -> TaffyAvailableSpace {
723        match space {
724            AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
725            AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
726            AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
727        }
728    }
729}
730
731impl From<TaffyAvailableSpace> for AvailableSpace {
732    fn from(space: TaffyAvailableSpace) -> AvailableSpace {
733        match space {
734            TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
735            TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
736            TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
737        }
738    }
739}
740
741impl From<Pixels> for AvailableSpace {
742    fn from(pixels: Pixels) -> Self {
743        AvailableSpace::Definite(pixels)
744    }
745}
746
747impl From<Size<Pixels>> for Size<AvailableSpace> {
748    fn from(size: Size<Pixels>) -> Self {
749        Size {
750            width: AvailableSpace::Definite(size.width),
751            height: AvailableSpace::Definite(size.height),
752        }
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    #[test]
761    fn border_widths_to_taffy_use_stroke_snapping() {
762        let border_widths = Edges {
763            top: Pixels(0.0).into(),
764            right: Pixels(0.4).into(),
765            bottom: Pixels(0.5).into(),
766            left: Pixels(1.6).into(),
767        };
768        let taffy_border = border_widths_to_taffy(&border_widths, Pixels(16.0), 1.0);
769
770        assert_eq!(
771            taffy_border.top,
772            taffy::style::LengthPercentage::length(0.0)
773        );
774        assert_eq!(
775            taffy_border.right,
776            taffy::style::LengthPercentage::length(1.0)
777        );
778        assert_eq!(
779            taffy_border.bottom,
780            taffy::style::LengthPercentage::length(1.0)
781        );
782        assert_eq!(
783            taffy_border.left,
784            taffy::style::LengthPercentage::length(2.0)
785        );
786    }
787}