crates/gpui/Cargo.toml 🔗
@@ -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"
Mikayla Maki and Anthony created
Release Notes:
- N/A
---------
Co-authored-by: Anthony <anthony@zed.dev>
crates/gpui/Cargo.toml | 4 +
crates/gpui/examples/grid_layout.rs | 80 ++++++++++++++++++++++
crates/gpui/src/geometry.rs | 33 +++++++++
crates/gpui/src/style.rs | 23 ++++++
crates/gpui/src/styled.rs | 109 ++++++++++++++++++++++++++++++
crates/gpui/src/taffy.rs | 35 +++++++++
6 files changed, 278 insertions(+), 6 deletions(-)
@@ -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"
@@ -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);
+ });
+}
@@ -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
@@ -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,
@@ -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 {
@@ -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()
}
}
}