1use crate::{
13 AnyView, AnyWindowHandle, App, AppCell, AppContext, AssetRegistry, AssetSource,
14 BackgroundExecutor, Bounds, Context, Entity, EntityId, EventEmitter, ForegroundExecutor,
15 Global, Hsla, Pixels, PlatformHeadlessRenderer, PlatformTextSystem, Render, Reservation,
16 ScaledPixels, Size, Task, TestDispatcher, TestPlatform, TextSystem, Window, WindowBounds,
17 WindowHandle, WindowOptions,
18 app::{GpuiBorrow, GpuiMode},
19};
20use anyhow::Result;
21use image::RgbaImage;
22use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
23
24pub struct HeadlessAppContext {
48 pub app: Rc<AppCell>,
50 pub background_executor: BackgroundExecutor,
52 pub foreground_executor: ForegroundExecutor,
54 dispatcher: TestDispatcher,
55 text_system: Arc<TextSystem>,
56}
57
58impl HeadlessAppContext {
59 pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
64 Self::with_platform(platform_text_system, Arc::new(()), || Ok(None))
65 }
66
67 pub fn with_asset_source(
69 platform_text_system: Arc<dyn PlatformTextSystem>,
70 asset_source: Arc<dyn AssetSource>,
71 ) -> Self {
72 Self::with_platform(platform_text_system, asset_source, || Ok(None))
73 }
74
75 pub fn with_platform(
81 platform_text_system: Arc<dyn PlatformTextSystem>,
82 asset_source: Arc<dyn AssetSource>,
83 renderer_factory: impl Fn() -> Result<Option<Box<dyn PlatformHeadlessRenderer>>> + 'static,
84 ) -> Self {
85 let seed = std::env::var("SEED")
86 .ok()
87 .and_then(|s| s.parse().ok())
88 .unwrap_or(0);
89
90 let dispatcher = TestDispatcher::new(seed);
91 let arc_dispatcher = Arc::new(dispatcher.clone());
92 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
93 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
94
95 let renderer_factory: Box<dyn Fn() -> Result<Option<Box<dyn PlatformHeadlessRenderer>>>> =
96 Box::new(renderer_factory);
97 let platform = TestPlatform::with_platform(
98 background_executor.clone(),
99 foreground_executor.clone(),
100 platform_text_system.clone(),
101 Some(renderer_factory),
102 );
103
104 let text_system = Arc::new(TextSystem::new(platform_text_system));
105 let http_client = crate::http_client::FakeHttpClient::with_404_response();
106 let app = App::new_app(
107 platform,
108 AssetRegistry::from(asset_source).into(),
109 http_client,
110 );
111 app.borrow_mut().mode = GpuiMode::test();
112
113 Self {
114 app,
115 background_executor,
116 foreground_executor,
117 dispatcher,
118 text_system,
119 }
120 }
121
122 pub fn open_window<V: Render + 'static>(
128 &mut self,
129 size: Size<Pixels>,
130 build_root: impl FnOnce(&mut Window, &mut App) -> Entity<V>,
131 ) -> Result<WindowHandle<V>> {
132 use crate::{point, px};
133
134 let bounds = Bounds {
135 origin: point(px(0.0), px(0.0)),
136 size,
137 };
138
139 let mut cx = self.app.borrow_mut();
140 cx.open_window(
141 WindowOptions::new()
142 .window_bounds(Some(WindowBounds::Windowed(bounds)))
143 .focus(false)
144 .show(false),
145 build_root,
146 )
147 }
148
149 pub fn run_until_parked(&self) {
151 self.dispatcher.run_until_parked();
152 }
153
154 pub fn advance_clock(&self, duration: Duration) {
156 self.dispatcher.advance_clock(duration);
157 }
158
159 pub fn allow_parking(&self) {
164 self.dispatcher.allow_parking();
165 }
166
167 pub fn forbid_parking(&self) {
169 self.dispatcher.forbid_parking();
170 }
171
172 pub fn update<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
174 let mut app = self.app.borrow_mut();
175 f(&mut app)
176 }
177
178 pub fn update_window<R>(
187 &mut self,
188 window: AnyWindowHandle,
189 f: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
190 ) -> Result<R> {
191 let mut app = self.app.borrow_mut();
192 app.update_window(window, f)
193 }
194
195 pub fn debug_bounds(
201 &mut self,
202 window: AnyWindowHandle,
203 selector: &str,
204 ) -> Result<Option<Bounds<Pixels>>> {
205 self.update_window(window, |_, window, _| {
206 window.rendered_frame.debug_bounds.get(selector).copied()
207 })
208 }
209
210 pub fn solid_quad_bounds(
218 &mut self,
219 window: AnyWindowHandle,
220 color: Hsla,
221 ) -> Result<Vec<Bounds<ScaledPixels>>> {
222 let color = color.into();
223 self.update_window(window, |_, window, _| {
224 window
225 .rendered_frame
226 .scene
227 .quads
228 .iter()
229 .filter(|quad| quad.background.solid == color)
230 .map(|quad| quad.bounds)
231 .collect()
232 })
233 }
234
235 pub fn glyph_bounds(
243 &mut self,
244 window: AnyWindowHandle,
245 color: Hsla,
246 ) -> Result<Vec<Bounds<ScaledPixels>>> {
247 let color = color.into();
248 self.update_window(window, |_, window, _| {
249 window
250 .rendered_frame
251 .scene
252 .monochrome_sprites
253 .iter()
254 .filter(|sprite| sprite.color == color)
255 .map(|sprite| sprite.bounds)
256 .collect()
257 })
258 }
259
260 pub fn underline_bounds(
268 &mut self,
269 window: AnyWindowHandle,
270 color: Hsla,
271 ) -> Result<Vec<Bounds<ScaledPixels>>> {
272 let color = color.into();
273 self.update_window(window, |_, window, _| {
274 window
275 .rendered_frame
276 .scene
277 .underlines
278 .iter()
279 .filter(|underline| underline.color == color)
280 .map(|underline| underline.bounds)
281 .collect()
282 })
283 }
284
285 pub fn capture_screenshot(&mut self, window: AnyWindowHandle) -> Result<RgbaImage> {
293 let mut app = self.app.borrow_mut();
294 app.update_window(window, |_, window, _| window.render_to_image())?
295 }
296
297 pub fn text_system(&self) -> &Arc<TextSystem> {
299 &self.text_system
300 }
301
302 pub fn background_executor(&self) -> &BackgroundExecutor {
304 &self.background_executor
305 }
306
307 pub fn foreground_executor(&self) -> &ForegroundExecutor {
309 &self.foreground_executor
310 }
311}
312
313impl Drop for HeadlessAppContext {
314 fn drop(&mut self) {
315 self.app.borrow_mut().shutdown();
318 }
319}
320
321impl AppContext for HeadlessAppContext {
322 fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
323 let mut app = self.app.borrow_mut();
324 app.new(build_entity)
325 }
326
327 fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
328 let mut app = self.app.borrow_mut();
329 app.reserve_entity()
330 }
331
332 fn insert_entity<T: 'static>(
333 &mut self,
334 reservation: Reservation<T>,
335 build_entity: impl FnOnce(&mut Context<T>) -> T,
336 ) -> Entity<T> {
337 let mut app = self.app.borrow_mut();
338 app.insert_entity(reservation, build_entity)
339 }
340
341 fn update_entity<T: 'static, R>(
342 &mut self,
343 handle: &Entity<T>,
344 update: impl FnOnce(&mut T, &mut Context<T>) -> R,
345 ) -> R {
346 let mut app = self.app.borrow_mut();
347 app.update_entity(handle, update)
348 }
349
350 fn as_mut<'a, T>(&'a mut self, _: &Entity<T>) -> GpuiBorrow<'a, T>
351 where
352 T: 'static,
353 {
354 panic!("Cannot use as_mut with HeadlessAppContext. Call update() instead.")
355 }
356
357 fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
358 where
359 T: 'static,
360 {
361 let app = self.app.borrow();
362 app.read_entity(handle, read)
363 }
364
365 fn notify(&mut self, entity_id: EntityId) {
366 let mut app = self.app.borrow_mut();
367 app.notify(entity_id);
368 }
369
370 fn emit<EntityType, EventType>(&mut self, entity: &Entity<EntityType>, event: EventType)
371 where
372 EntityType: EventEmitter<EventType>,
373 EventType: 'static,
374 {
375 let mut app = self.app.borrow_mut();
376 app.emit(entity, event)
377 }
378
379 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
380 where
381 F: FnOnce(AnyView, &mut Window, &mut App) -> T,
382 {
383 let mut lock = self.app.borrow_mut();
384 lock.update_window(window, f)
385 }
386
387 fn with_window<R>(
388 &mut self,
389 entity_id: EntityId,
390 f: impl FnOnce(&mut Window, &mut App) -> R,
391 ) -> Option<R> {
392 let mut lock = self.app.borrow_mut();
393 lock.with_window(entity_id, f)
394 }
395
396 fn read_window<T, R>(
397 &self,
398 window: &WindowHandle<T>,
399 read: impl FnOnce(Entity<T>, &App) -> R,
400 ) -> Result<R>
401 where
402 T: 'static,
403 {
404 let app = self.app.borrow();
405 app.read_window(window, read)
406 }
407
408 fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
409 where
410 R: Send + 'static,
411 {
412 self.background_executor.spawn(future)
413 }
414
415 fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
416 where
417 G: Global,
418 {
419 let app = self.app.borrow();
420 app.read_global(callback)
421 }
422
423 fn insert_global_entity<T: 'static>(&mut self, entity: Entity<T>) {
424 let mut app = self.app.borrow_mut();
425 app.insert_global_entity(entity)
426 }
427
428 fn remove_global_entity<T: 'static>(&mut self, entity: &Entity<T>) {
429 let mut app = self.app.borrow_mut();
430 app.remove_global_entity(entity)
431 }
432
433 fn global_entities<T: 'static>(&self) -> impl Iterator<Item = Entity<T>> {
434 let app = self.app.borrow();
435 let iter = app.global_entities();
436 iter.collect::<Vec<_>>().into_iter()
438 }
439}