Skip to main content

gpui_authoring/elements/
container_query.rs

1//! A container query element, in the spirit of CSS container queries.
2//! The element's own size is determined solely by its style and the space
3//! offered by its parent.
4
5use refineable::Refineable as _;
6
7use crate::{
8    AnyElement, App, AvailableSpace, Bounds, Element, ElementId, GlobalElementId, IntoElement,
9    LayoutId, Pixels, Size, Style, StyleRefinement, Styled, Window, relative,
10};
11
12/// Construct a container query element with the given render callback.
13/// The callback receives the size the element was assigned during layout and
14/// returns the contents to display within it.
15///
16/// By default the element fills its parent (equivalent to `.size_full()`);
17/// use the [`Styled`] methods to size it differently. Because the contents
18/// don't exist until after layout, they cannot influence the element's size.
19///
20/// # Example
21///
22/// ```
23/// # use gpui::{container_query, div, px, IntoElement, ParentElement};
24/// container_query(|size, _window, _cx| {
25///     if size.width < px(240.) {
26///         div().child("Narrow layout")
27///     } else {
28///         div().child("Wide layout")
29///     }
30/// });
31/// ```
32pub fn container_query<E>(
33    render: impl 'static + FnOnce(Size<Pixels>, &mut Window, &mut App) -> E,
34) -> ContainerQuery
35where
36    E: IntoElement,
37{
38    let mut base_style = StyleRefinement::default();
39    base_style.size.width = Some(relative(1.).into());
40    base_style.size.height = Some(relative(1.).into());
41
42    ContainerQuery {
43        render: Some(Box::new(|size, window, cx| {
44            render(size, window, cx).into_any_element()
45        })),
46        style: base_style,
47    }
48}
49
50/// A container query element, created with [`container_query`].
51pub struct ContainerQuery {
52    render: Option<Box<dyn FnOnce(Size<Pixels>, &mut Window, &mut App) -> AnyElement>>,
53    style: StyleRefinement,
54}
55
56impl Element for ContainerQuery {
57    type RequestLayoutState = ();
58    type PrepaintState = Option<AnyElement>;
59
60    fn id(&self) -> Option<ElementId> {
61        None
62    }
63
64    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
65        None
66    }
67
68    fn request_layout(
69        &mut self,
70        _id: Option<&GlobalElementId>,
71        window: &mut Window,
72        cx: &mut App,
73    ) -> (LayoutId, Self::RequestLayoutState) {
74        let mut style = Style::default();
75        style.refine(&self.style);
76        let layout_id = window.request_layout(style, [], cx);
77        (layout_id, ())
78    }
79
80    fn prepaint(
81        &mut self,
82        _id: Option<&GlobalElementId>,
83        bounds: Bounds<Pixels>,
84        _request_layout: &mut Self::RequestLayoutState,
85        window: &mut Window,
86        cx: &mut App,
87    ) -> Option<AnyElement> {
88        let render = self.render.take()?;
89        let mut child = render(bounds.size, window, cx);
90        child.layout_as_root(bounds.size.map(AvailableSpace::Definite), window, cx);
91        child.prepaint_at(bounds.origin, window, cx);
92        Some(child)
93    }
94
95    fn paint(
96        &mut self,
97        _id: Option<&GlobalElementId>,
98        _bounds: Bounds<Pixels>,
99        _request_layout: &mut Self::RequestLayoutState,
100        prepaint: &mut Self::PrepaintState,
101        window: &mut Window,
102        cx: &mut App,
103    ) {
104        if let Some(child) = prepaint {
105            child.paint(window, cx);
106        }
107    }
108}
109
110impl IntoElement for ContainerQuery {
111    type Element = Self;
112
113    fn into_element(self) -> Self::Element {
114        self
115    }
116}
117
118impl Styled for ContainerQuery {
119    fn style(&mut self) -> &mut StyleRefinement {
120        &mut self.style
121    }
122}