Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:35:50.833Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

taffy.rs

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