1use refineable::Refineable as _;
6
7use crate::{
8 AnyElement, App, AvailableSpace, Bounds, Element, ElementId, GlobalElementId,
9 InspectorElementId, IntoElement, LayoutId, Pixels, Size, Style, StyleRefinement, Styled,
10 Window, relative,
11};
12
13pub fn container_query<E>(
34 render: impl 'static + FnOnce(Size<Pixels>, &mut Window, &mut App) -> E,
35) -> ContainerQuery
36where
37 E: IntoElement,
38{
39 let mut base_style = StyleRefinement::default();
40 base_style.size.width = Some(relative(1.).into());
41 base_style.size.height = Some(relative(1.).into());
42
43 ContainerQuery {
44 render: Some(Box::new(|size, window, cx| {
45 render(size, window, cx).into_any_element()
46 })),
47 style: base_style,
48 }
49}
50
51pub struct ContainerQuery {
53 render: Option<Box<dyn FnOnce(Size<Pixels>, &mut Window, &mut App) -> AnyElement>>,
54 style: StyleRefinement,
55}
56
57impl Element for ContainerQuery {
58 type RequestLayoutState = ();
59 type PrepaintState = Option<AnyElement>;
60
61 fn id(&self) -> Option<ElementId> {
62 None
63 }
64
65 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
66 None
67 }
68
69 fn request_layout(
70 &mut self,
71 _id: Option<&GlobalElementId>,
72 _inspector_id: Option<&InspectorElementId>,
73 window: &mut Window,
74 cx: &mut App,
75 ) -> (LayoutId, Self::RequestLayoutState) {
76 let mut style = Style::default();
77 style.refine(&self.style);
78 let layout_id = window.request_layout(style, [], cx);
79 (layout_id, ())
80 }
81
82 fn prepaint(
83 &mut self,
84 _id: Option<&GlobalElementId>,
85 _inspector_id: Option<&InspectorElementId>,
86 bounds: Bounds<Pixels>,
87 _request_layout: &mut Self::RequestLayoutState,
88 window: &mut Window,
89 cx: &mut App,
90 ) -> Option<AnyElement> {
91 let render = self.render.take()?;
92 let mut child = render(bounds.size, window, cx);
93 child.layout_as_root(bounds.size.map(AvailableSpace::Definite), window, cx);
94 child.prepaint_at(bounds.origin, window, cx);
95 Some(child)
96 }
97
98 fn paint(
99 &mut self,
100 _id: Option<&GlobalElementId>,
101 _inspector_id: Option<&InspectorElementId>,
102 _bounds: Bounds<Pixels>,
103 _request_layout: &mut Self::RequestLayoutState,
104 prepaint: &mut Self::PrepaintState,
105 window: &mut Window,
106 cx: &mut App,
107 ) {
108 if let Some(child) = prepaint {
109 child.paint(window, cx);
110 }
111 }
112}
113
114impl IntoElement for ContainerQuery {
115 type Element = Self;
116
117 fn into_element(self) -> Self::Element {
118 self
119 }
120}
121
122impl Styled for ContainerQuery {
123 fn style(&mut self) -> &mut StyleRefinement {
124 &mut self.style
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use crate::{
131 Context, IntoElement, ParentElement, Pixels, Render, Size, Styled, TestAppContext, Window,
132 container_query, div, px, size,
133 };
134 use std::cell::Cell;
135 use std::rc::Rc;
136
137 struct ContainerQueryView {
138 last_size: Rc<Cell<Option<Size<Pixels>>>>,
139 }
140
141 impl Render for ContainerQueryView {
142 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
143 let last_size = self.last_size.clone();
144 container_query(move |container_size, _window, _cx| {
145 last_size.set(Some(container_size));
146 if container_size.width < px(400.) {
147 div().size_full().child("narrow")
148 } else {
149 div().size_full().child("wide")
150 }
151 })
152 }
153 }
154
155 #[gpui::test]
156 fn container_query_builds_children_from_measured_size(cx: &mut TestAppContext) {
157 let last_size = Rc::new(Cell::new(None));
158 let (_, cx) = cx.add_window_view({
159 let last_size = last_size.clone();
160 move |_, _| ContainerQueryView { last_size }
161 });
162
163 cx.simulate_resize(size(px(640.), px(480.)));
164 let wide = last_size
165 .get()
166 .expect("container_query should run after the wide resize");
167 assert!(
168 wide.width >= px(400.),
169 "wide window should offer a container at least 400px wide, got {}",
170 wide.width
171 );
172
173 cx.simulate_resize(size(px(320.), px(480.)));
174 let narrow = last_size
175 .get()
176 .expect("container_query should run after the narrow resize");
177 assert!(
178 narrow.width < px(400.),
179 "narrow window should offer a container under 400px wide, got {}",
180 narrow.width
181 );
182 assert_ne!(
183 wide, narrow,
184 "resizing the window should rebuild children from a new measured size"
185 );
186 }
187
188 #[gpui::test]
189 fn container_query_honors_explicit_size(cx: &mut TestAppContext) {
190 let last_size = Rc::new(Cell::new(None));
191 struct SizedQuery {
192 last_size: Rc<Cell<Option<Size<Pixels>>>>,
193 }
194
195 impl Render for SizedQuery {
196 fn render(
197 &mut self,
198 _window: &mut Window,
199 _cx: &mut Context<Self>,
200 ) -> impl IntoElement {
201 let last_size = self.last_size.clone();
202 container_query(move |container_size, _window, _cx| {
203 last_size.set(Some(container_size));
204 div().size_full()
205 })
206 .w(px(240.))
207 .h(px(80.))
208 }
209 }
210
211 let (_, cx) = cx.add_window_view({
212 let last_size = last_size.clone();
213 move |_, _| SizedQuery { last_size }
214 });
215 cx.simulate_resize(size(px(800.), px(600.)));
216 assert_eq!(
217 last_size.get(),
218 Some(size(px(240.), px(80.))),
219 "container_query should measure from its own style, not only the parent offer"
220 );
221 }
222}