Add grid support to GPUI (#36153)

Release Notes:

- N/A

---------

Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Mikayla Maki 2025-08-13 17:01:17 -07:00 committed by GitHub
parent e67b2da20c
commit 32f9de6124
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 278 additions and 6 deletions

View file

@ -305,3 +305,7 @@ path = "examples/uniform_list.rs"
[[example]]
name = "window_shadow"
path = "examples/window_shadow.rs"
[[example]]
name = "grid_layout"
path = "examples/grid_layout.rs"

View file

@ -0,0 +1,80 @@
use gpui::{
App, Application, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*,
px, rgb, size,
};
// https://en.wikipedia.org/wiki/Holy_grail_(web_design)
struct HolyGrailExample {}
impl Render for HolyGrailExample {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let block = |color: Hsla| {
div()
.size_full()
.bg(color)
.border_1()
.border_dashed()
.rounded_md()
.border_color(gpui::white())
.items_center()
};
div()
.gap_1()
.grid()
.bg(rgb(0x505050))
.size(px(500.0))
.shadow_lg()
.border_1()
.size_full()
.grid_cols(5)
.grid_rows(5)
.child(
block(gpui::white())
.row_span(1)
.col_span_full()
.child("Header"),
)
.child(
block(gpui::red())
.col_span(1)
.h_56()
.child("Table of contents"),
)
.child(
block(gpui::green())
.col_span(3)
.row_span(3)
.child("Content"),
)
.child(
block(gpui::blue())
.col_span(1)
.row_span(3)
.child("AD :(")
.text_color(gpui::white()),
)
.child(
block(gpui::black())
.row_span(1)
.col_span_full()
.text_color(gpui::white())
.child("Footer"),
)
}
}
fn main() {
Application::new().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|_, cx| cx.new(|_| HolyGrailExample {}),
)
.unwrap();
cx.activate(true);
});
}

View file

@ -9,12 +9,14 @@ use refineable::Refineable;
use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::borrow::Cow;
use std::ops::Range;
use std::{
cmp::{self, PartialOrd},
fmt::{self, Display},
hash::Hash,
ops::{Add, Div, Mul, MulAssign, Neg, Sub},
};
use taffy::prelude::{TaffyGridLine, TaffyGridSpan};
use crate::{App, DisplayId};
@ -3608,6 +3610,37 @@ impl From<()> for Length {
}
}
/// A location in a grid layout.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, JsonSchema, Default)]
pub struct GridLocation {
/// The rows this item uses within the grid.
pub row: Range<GridPlacement>,
/// The columns this item uses within the grid.
pub column: Range<GridPlacement>,
}
/// The placement of an item within a grid layout's column or row.
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema, Default)]
pub enum GridPlacement {
/// The grid line index to place this item.
Line(i16),
/// The number of grid lines to span.
Span(u16),
/// Automatically determine the placement, equivalent to Span(1)
#[default]
Auto,
}
impl From<GridPlacement> for taffy::GridPlacement {
fn from(placement: GridPlacement) -> Self {
match placement {
GridPlacement::Line(index) => taffy::GridPlacement::from_line_index(index),
GridPlacement::Span(span) => taffy::GridPlacement::from_span(span),
GridPlacement::Auto => taffy::GridPlacement::Auto,
}
}
}
/// Provides a trait for types that can calculate half of their value.
///
/// The `Half` trait is used for types that can be evenly divided, returning a new instance of the same type

View file

@ -7,7 +7,7 @@ use std::{
use crate::{
AbsoluteLength, App, Background, BackgroundTag, BorderStyle, Bounds, ContentMask, Corners,
CornersRefinement, CursorStyle, DefiniteLength, DevicePixels, Edges, EdgesRefinement, Font,
FontFallbacks, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point,
FontFallbacks, FontFeatures, FontStyle, FontWeight, GridLocation, Hsla, Length, Pixels, Point,
PointRefinement, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, Window, black, phi,
point, quad, rems, size,
};
@ -260,6 +260,17 @@ pub struct Style {
/// The opacity of this element
pub opacity: Option<f32>,
/// The grid columns of this element
/// Equivalent to the Tailwind `grid-cols-<number>`
pub grid_cols: Option<u16>,
/// The row span of this element
/// Equivalent to the Tailwind `grid-rows-<number>`
pub grid_rows: Option<u16>,
/// The grid location of this element
pub grid_location: Option<GridLocation>,
/// Whether to draw a red debugging outline around this element
#[cfg(debug_assertions)]
pub debug: bool,
@ -275,6 +286,13 @@ impl Styled for StyleRefinement {
}
}
impl StyleRefinement {
/// The grid location of this element
pub fn grid_location_mut(&mut self) -> &mut GridLocation {
self.grid_location.get_or_insert_default()
}
}
/// The value of the visibility property, similar to the CSS property `visibility`
#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum Visibility {
@ -757,6 +775,9 @@ impl Default for Style {
text: TextStyleRefinement::default(),
mouse_cursor: None,
opacity: None,
grid_rows: None,
grid_cols: None,
grid_location: None,
#[cfg(debug_assertions)]
debug: false,

View file

@ -1,8 +1,8 @@
use crate::{
self as gpui, AbsoluteLength, AlignContent, AlignItems, BorderStyle, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla,
JustifyContent, Length, SharedString, StrikethroughStyle, StyleRefinement, TextAlign,
TextOverflow, TextStyleRefinement, UnderlineStyle, WhiteSpace, px, relative, rems,
DefiniteLength, Display, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight,
GridPlacement, Hsla, JustifyContent, Length, SharedString, StrikethroughStyle, StyleRefinement,
TextAlign, TextOverflow, TextStyleRefinement, UnderlineStyle, WhiteSpace, px, relative, rems,
};
pub use gpui_macros::{
border_style_methods, box_shadow_style_methods, cursor_style_methods, margin_style_methods,
@ -46,6 +46,13 @@ pub trait Styled: Sized {
self
}
/// Sets the display type of the element to `grid`.
/// [Docs](https://tailwindcss.com/docs/display)
fn grid(mut self) -> Self {
self.style().display = Some(Display::Grid);
self
}
/// Sets the whitespace of the element to `normal`.
/// [Docs](https://tailwindcss.com/docs/whitespace#normal)
fn whitespace_normal(mut self) -> Self {
@ -640,6 +647,102 @@ pub trait Styled: Sized {
self
}
/// Sets the grid columns of this element.
fn grid_cols(mut self, cols: u16) -> Self {
self.style().grid_cols = Some(cols);
self
}
/// Sets the grid rows of this element.
fn grid_rows(mut self, rows: u16) -> Self {
self.style().grid_rows = Some(rows);
self
}
/// Sets the column start of this element.
fn col_start(mut self, start: i16) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.column.start = GridPlacement::Line(start);
self
}
/// Sets the column start of this element to auto.
fn col_start_auto(mut self) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.column.start = GridPlacement::Auto;
self
}
/// Sets the column end of this element.
fn col_end(mut self, end: i16) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.column.end = GridPlacement::Line(end);
self
}
/// Sets the column end of this element to auto.
fn col_end_auto(mut self) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.column.end = GridPlacement::Auto;
self
}
/// Sets the column span of this element.
fn col_span(mut self, span: u16) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.column = GridPlacement::Span(span)..GridPlacement::Span(span);
self
}
/// Sets the row span of this element.
fn col_span_full(mut self) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.column = GridPlacement::Line(1)..GridPlacement::Line(-1);
self
}
/// Sets the row start of this element.
fn row_start(mut self, start: i16) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.row.start = GridPlacement::Line(start);
self
}
/// Sets the row start of this element to "auto"
fn row_start_auto(mut self) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.row.start = GridPlacement::Auto;
self
}
/// Sets the row end of this element.
fn row_end(mut self, end: i16) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.row.end = GridPlacement::Line(end);
self
}
/// Sets the row end of this element to "auto"
fn row_end_auto(mut self) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.row.end = GridPlacement::Auto;
self
}
/// Sets the row span of this element.
fn row_span(mut self, span: u16) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.row = GridPlacement::Span(span)..GridPlacement::Span(span);
self
}
/// Sets the row span of this element.
fn row_span_full(mut self) -> Self {
let grid_location = self.style().grid_location_mut();
grid_location.row = GridPlacement::Line(1)..GridPlacement::Line(-1);
self
}
/// Draws a debug border around this element.
#[cfg(debug_assertions)]
fn debug(mut self) -> Self {

View file

@ -3,7 +3,7 @@ use crate::{
};
use collections::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use std::fmt::Debug;
use std::{fmt::Debug, ops::Range};
use taffy::{
TaffyTree, TraversePartialTree as _,
geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
@ -251,6 +251,25 @@ trait ToTaffy<Output> {
impl ToTaffy<taffy::style::Style> for Style {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
use taffy::style_helpers::{fr, length, minmax, repeat};
fn to_grid_line(
placement: &Range<crate::GridPlacement>,
) -> taffy::Line<taffy::GridPlacement> {
taffy::Line {
start: placement.start.into(),
end: placement.end.into(),
}
}
fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
unit: &Option<u16>,
) -> Vec<taffy::GridTemplateComponent<T>> {
// grid-template-columns: repeat(<number>, minmax(0, 1fr));
unit.map(|count| vec![repeat(count, vec![minmax(length(0.0), fr(1.0))])])
.unwrap_or_default()
}
taffy::style::Style {
display: self.display.into(),
overflow: self.overflow.into(),
@ -274,7 +293,19 @@ impl ToTaffy<taffy::style::Style> for Style {
flex_basis: self.flex_basis.to_taffy(rem_size),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
grid_template_rows: to_grid_repeat(&self.grid_rows),
grid_template_columns: to_grid_repeat(&self.grid_cols),
grid_row: self
.grid_location
.as_ref()
.map(|location| to_grid_line(&location.row))
.unwrap_or_default(),
grid_column: self
.grid_location
.as_ref()
.map(|location| to_grid_line(&location.column))
.unwrap_or_default(),
..Default::default()
}
}
}