gpui: Add grid repeat min content API (#44973)

Smit Barmase created

Required for https://github.com/zed-industries/zed/pull/44712

We started using `grid` for Markdown tables instead of flex. This
resulted in tables having a width of 0 inside popovers, since popovers
are laid out using `AvailableSpace::MinContent`.

One way to fix this is to lay out popovers using `MaxContent` instead.
But that would affect all Markdown rendered in popovers and could change
how popovers look, or regress things.

The other option is to fix it where the problem actually is:
`repeat(count, vec![minmax(length(0.0), fr(1.0))])`. Since the minimum
width here is `0`, laying things out with `MinContent` causes the
Markdown table to shrink completely. What we want instead is for the
minimum width to be the min-content size, but only for Markdown rendered
inside popovers.

This PR does exactly that, without interfering with the `grid_cols` API,
which intentionally follows a TailwindCSS-like convention. See
https://github.com/zed-industries/zed/pull/44368 for context.

Release Notes:

- N/A

Change summary

crates/gpui/src/style.rs  |  5 +++++
crates/gpui/src/styled.rs |  7 +++++++
crates/gpui/src/taffy.rs  | 15 ++++++++++++++-
3 files changed, 26 insertions(+), 1 deletion(-)

Detailed changes

crates/gpui/src/style.rs 🔗

@@ -265,6 +265,10 @@ pub struct Style {
     /// Equivalent to the Tailwind `grid-cols-<number>`
     pub grid_cols: Option<u16>,
 
+    /// The grid columns with min-content minimum sizing.
+    /// Unlike grid_cols, it won't shrink to width 0 in AvailableSpace::MinContent constraints.
+    pub grid_cols_min_content: Option<u16>,
+
     /// The row span of this element
     /// Equivalent to the Tailwind `grid-rows-<number>`
     pub grid_rows: Option<u16>,
@@ -772,6 +776,7 @@ impl Default for Style {
             opacity: None,
             grid_rows: None,
             grid_cols: None,
+            grid_cols_min_content: None,
             grid_location: None,
 
             #[cfg(debug_assertions)]

crates/gpui/src/styled.rs 🔗

@@ -637,6 +637,13 @@ pub trait Styled: Sized {
         self
     }
 
+    /// Sets the grid columns with min-content minimum sizing.
+    /// Unlike grid_cols, it won't shrink to width 0 in AvailableSpace::MinContent constraints.
+    fn grid_cols_min_content(mut self, cols: u16) -> Self {
+        self.style().grid_cols_min_content = Some(cols);
+        self
+    }
+
     /// Sets the grid rows of this element.
     fn grid_rows(mut self, rows: u16) -> Self {
         self.style().grid_rows = Some(rows);

crates/gpui/src/taffy.rs 🔗

@@ -8,6 +8,7 @@ use std::{fmt::Debug, ops::Range};
 use taffy::{
     TaffyTree, TraversePartialTree as _,
     geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
+    prelude::min_content,
     style::AvailableSpace as TaffyAvailableSpace,
     tree::NodeId,
 };
@@ -314,6 +315,14 @@ impl ToTaffy<taffy::style::Style> for Style {
                 .unwrap_or_default()
         }
 
+        fn to_grid_repeat_min_content<T: taffy::style::CheapCloneStr>(
+            unit: &Option<u16>,
+        ) -> Vec<taffy::GridTemplateComponent<T>> {
+            // grid-template-columns: repeat(<number>, minmax(min-content, 1fr));
+            unit.map(|count| vec![repeat(count, vec![minmax(min_content(), fr(1.0))])])
+                .unwrap_or_default()
+        }
+
         taffy::style::Style {
             display: self.display.into(),
             overflow: self.overflow.into(),
@@ -338,7 +347,11 @@ impl ToTaffy<taffy::style::Style> for Style {
             flex_grow: self.flex_grow,
             flex_shrink: self.flex_shrink,
             grid_template_rows: to_grid_repeat(&self.grid_rows),
-            grid_template_columns: to_grid_repeat(&self.grid_cols),
+            grid_template_columns: if self.grid_cols_min_content.is_some() {
+                to_grid_repeat_min_content(&self.grid_cols_min_content)
+            } else {
+                to_grid_repeat(&self.grid_cols)
+            },
             grid_row: self
                 .grid_location
                 .as_ref()