Skip to main content

gpui_core/
taffy.rs

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