Skip to main content

gpui/elements/
container_query.rs

1//! An element that builds its contents from its laid-out size.
2//!
3//! [`container_query`] measures the element first, then calls its closure with that size. The
4//! closure's contents cannot change the size that triggered the call; use the surrounding style
5//! when the container itself needs a different size.
6
7use refineable::Refineable as _;
8
9use crate::{
10    AnyElement, App, AvailableSpace, Bounds, Element, ElementId, GlobalElementId,
11    InspectorElementId, IntoElement, LayoutId, Pixels, Size, Style, StyleRefinement, Styled,
12    Window, relative,
13};
14
15/// Creates a [`ContainerQuery`] whose contents depend on the size it is given.
16///
17/// Use it when a component should change its layout to fit the space its parent gives it, rather
18/// than the window's size. `render` receives the element's size, after layout has placed it, and
19/// returns the contents. The contents can't change the element's own size, because they are built
20/// after layout.
21///
22/// By default the element fills its parent, as `.size_full()` does. Use the [`Styled`] methods to
23/// size it differently.
24///
25/// # Example
26///
27/// ```
28/// use std::{cell::Cell, rc::Rc};
29///
30/// use gpui::{
31///     Context, IntoElement, Pixels, Render, TestApp, Window, container_query, div, prelude::*, px,
32/// };
33///
34/// struct Responsive {
35///     width: Rc<Cell<Option<Pixels>>>,
36/// }
37///
38/// impl Render for Responsive {
39///     fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
40///         let width = self.width.clone();
41///         container_query(move |size, _window, _cx| {
42///             width.set(Some(size.width));
43///             if size.width < px(240.) {
44///                 div().child("Narrow layout")
45///             } else {
46///                 div().child("Wide layout")
47///             }
48///         })
49///     }
50/// }
51///
52/// let width = Rc::new(Cell::new(None));
53/// let mut app = TestApp::new();
54/// let mut window = app.open_window({
55///     let width = width.clone();
56///     move |_window, _cx| Responsive { width }
57/// });
58///
59/// window.draw();
60/// assert!(width.get().is_some_and(|width| width > px(0.)));
61/// ```
62pub fn container_query<E>(
63    render: impl 'static + FnOnce(Size<Pixels>, &mut Window, &mut App) -> E,
64) -> ContainerQuery
65where
66    E: IntoElement,
67{
68    let mut base_style = StyleRefinement::default();
69    base_style.size.width = Some(relative(1.).into());
70    base_style.size.height = Some(relative(1.).into());
71
72    ContainerQuery {
73        render: Some(Box::new(|size, window, cx| {
74            render(size, window, cx).into_any_element()
75        })),
76        style: base_style,
77    }
78}
79
80/// A container query element, created with [`container_query`].
81pub struct ContainerQuery {
82    render: Option<Box<dyn FnOnce(Size<Pixels>, &mut Window, &mut App) -> AnyElement>>,
83    style: StyleRefinement,
84}
85
86impl Element for ContainerQuery {
87    type RequestLayoutState = ();
88    type PrepaintState = Option<AnyElement>;
89
90    fn id(&self) -> Option<ElementId> {
91        None
92    }
93
94    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
95        None
96    }
97
98    fn request_layout(
99        &mut self,
100        _id: Option<&GlobalElementId>,
101        _inspector_id: Option<&InspectorElementId>,
102        window: &mut Window,
103        cx: &mut App,
104    ) -> (LayoutId, Self::RequestLayoutState) {
105        let mut style = Style::default();
106        style.refine(&self.style);
107        let layout_id = window.request_layout(style, [], cx);
108        (layout_id, ())
109    }
110
111    fn prepaint(
112        &mut self,
113        _id: Option<&GlobalElementId>,
114        _inspector_id: Option<&InspectorElementId>,
115        bounds: Bounds<Pixels>,
116        _request_layout: &mut Self::RequestLayoutState,
117        window: &mut Window,
118        cx: &mut App,
119    ) -> Option<AnyElement> {
120        let render = self.render.take()?;
121        let mut child = render(bounds.size, window, cx);
122        child.layout_as_root(bounds.size.map(AvailableSpace::Definite), window, cx);
123        child.prepaint_at(bounds.origin, window, cx);
124        Some(child)
125    }
126
127    fn paint(
128        &mut self,
129        _id: Option<&GlobalElementId>,
130        _inspector_id: Option<&InspectorElementId>,
131        _bounds: Bounds<Pixels>,
132        _request_layout: &mut Self::RequestLayoutState,
133        prepaint: &mut Self::PrepaintState,
134        window: &mut Window,
135        cx: &mut App,
136    ) {
137        if let Some(child) = prepaint {
138            child.paint(window, cx);
139        }
140    }
141}
142
143impl IntoElement for ContainerQuery {
144    type Element = Self;
145
146    fn into_element(self) -> Self::Element {
147        self
148    }
149}
150
151impl Styled for ContainerQuery {
152    fn style(&mut self) -> &mut StyleRefinement {
153        &mut self.style
154    }
155}