Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 48 additions & 53 deletions crates/gpui/examples/grid_layout.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,63 @@
#![cfg_attr(target_family = "wasm", no_main)]

use gpui::{
App, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
App, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, container_query, div,
prelude::*, px, rgb, size,
};
use gpui_platform::application;

// https://en.wikipedia.org/wiki/Holy_grail_(web_design)
//
// Resize the window: the layout is chosen by `container_query` based on the
// measured size of the container, collapsing to a single stacked column when
// it becomes too narrow for the three-column grid.
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()
};
container_query(|container_size, _window, _cx| {
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"),
)
let header = block(gpui::white()).child(format!("Header — {}", container_size.width));
let table_of_contents = block(gpui::red()).child("Table of contents");
let content = block(gpui::green()).child("Content");
let ad = block(gpui::blue()).child("AD :(").text_color(gpui::white());
let footer = block(gpui::black())
.text_color(gpui::white())
.child("Footer");

let container = div().gap_1().bg(rgb(0x505050)).shadow_lg().size_full();

if container_size.width < px(400.) {
container
.flex()
.flex_col()
.child(header.h_12().flex_none())
.child(table_of_contents.h_20().flex_none())
.child(content.flex_1())
.child(ad.h_20().flex_none())
.child(footer.h_12().flex_none())
} else {
container
.grid()
.grid_cols(5)
.grid_rows(5)
.child(header.row_span(1).col_span_full())
.child(table_of_contents.col_span(1).h_56())
.child(content.col_span(3).row_span(3))
.child(ad.col_span(1).row_span(3))
.child(footer.row_span(1).col_span_full())
}
})
}
}

Expand Down
126 changes: 126 additions & 0 deletions crates/gpui/src/elements/container_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! A container query element, in the spirit of CSS container queries.
//! The element's own size is determined solely by its style and the space
//! offered by its parent.

use refineable::Refineable as _;

use crate::{
AnyElement, App, AvailableSpace, Bounds, Element, ElementId, GlobalElementId,
InspectorElementId, IntoElement, LayoutId, Pixels, Size, Style, StyleRefinement, Styled,
Window, relative,
};

/// Construct a container query element with the given render callback.
/// The callback receives the size the element was assigned during layout and
/// returns the contents to display within it.
///
/// By default the element fills its parent (equivalent to `.size_full()`);
/// use the [`Styled`] methods to size it differently. Because the contents
/// don't exist until after layout, they cannot influence the element's size.
///
/// # Example
///
/// ```
/// # use gpui::{container_query, div, px, IntoElement, ParentElement};
/// container_query(|size, _window, _cx| {
/// if size.width < px(240.) {
/// div().child("Narrow layout")
/// } else {
/// div().child("Wide layout")
/// }
/// });
/// ```
pub fn container_query<E>(
render: impl 'static + FnOnce(Size<Pixels>, &mut Window, &mut App) -> E,
) -> ContainerQuery
where
E: IntoElement,
{
let mut base_style = StyleRefinement::default();
base_style.size.width = Some(relative(1.).into());
base_style.size.height = Some(relative(1.).into());

ContainerQuery {
render: Some(Box::new(|size, window, cx| {
render(size, window, cx).into_any_element()
})),
style: base_style,
}
}

/// A container query element, created with [`container_query`].
pub struct ContainerQuery {
render: Option<Box<dyn FnOnce(Size<Pixels>, &mut Window, &mut App) -> AnyElement>>,
style: StyleRefinement,
}

impl Element for ContainerQuery {
type RequestLayoutState = ();
type PrepaintState = Option<AnyElement>;

fn id(&self) -> Option<ElementId> {
None
}

fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}

fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.refine(&self.style);
let layout_id = window.request_layout(style, [], cx);
(layout_id, ())
}

fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
let render = self.render.take()?;
let mut child = render(bounds.size, window, cx);
child.layout_as_root(bounds.size.map(AvailableSpace::Definite), window, cx);
child.prepaint_at(bounds.origin, window, cx);
Some(child)
}

fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
if let Some(child) = prepaint {
child.paint(window, cx);
}
}
}

impl IntoElement for ContainerQuery {
type Element = Self;

fn into_element(self) -> Self::Element {
self
}
}

impl Styled for ContainerQuery {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
2 changes: 2 additions & 0 deletions crates/gpui/src/elements/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod anchored;
mod animation;
mod canvas;
mod container_query;
mod deferred;
mod div;
mod image_cache;
Expand All @@ -14,6 +15,7 @@ mod uniform_list;
pub use anchored::*;
pub use animation::*;
pub use canvas::*;
pub use container_query::*;
pub use deferred::*;
pub use div::*;
pub use image_cache::*;
Expand Down
Loading