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