use crate::{
AnyView, AnyWindowHandle, App, AppCell, AppContext, AssetSource, BackgroundExecutor, Bounds,
Context, Entity, ForegroundExecutor, Global, Pixels, PlatformHeadlessRenderer,
PlatformTextSystem, Render, Reservation, Size, Task, TestDispatcher, TestPlatform, TextSystem,
Window, WindowBounds, WindowHandle, WindowOptions,
app::{GpuiBorrow, GpuiMode},
};
use anyhow::Result;
use image::RgbaImage;
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
pub struct HeadlessAppContext {
pub app: Rc<AppCell>,
pub background_executor: BackgroundExecutor,
pub foreground_executor: ForegroundExecutor,
dispatcher: TestDispatcher,
text_system: Arc<TextSystem>,
}
impl HeadlessAppContext {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
Self::with_platform(platform_text_system, Arc::new(()), || None)
}
pub fn with_asset_source(
platform_text_system: Arc<dyn PlatformTextSystem>,
asset_source: Arc<dyn AssetSource>,
) -> Self {
Self::with_platform(platform_text_system, asset_source, || None)
}
pub fn with_platform(
platform_text_system: Arc<dyn PlatformTextSystem>,
asset_source: Arc<dyn AssetSource>,
renderer_factory: impl Fn() -> Option<Box<dyn PlatformHeadlessRenderer>> + 'static,
) -> Self {
let seed = std::env::var("SEED")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let dispatcher = TestDispatcher::new(seed);
let arc_dispatcher = Arc::new(dispatcher.clone());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
let renderer_factory: Box<dyn Fn() -> Option<Box<dyn PlatformHeadlessRenderer>>> =
Box::new(renderer_factory);
let platform = TestPlatform::with_platform(
background_executor.clone(),
foreground_executor.clone(),
platform_text_system.clone(),
Some(renderer_factory),
);
let text_system = Arc::new(TextSystem::new(platform_text_system));
let http_client = http_client::FakeHttpClient::with_404_response();
let app = App::new_app(platform, asset_source, http_client);
app.borrow_mut().mode = GpuiMode::test();
Self {
app,
background_executor,
foreground_executor,
dispatcher,
text_system,
}
}
pub fn open_window<V: Render + 'static>(
&mut self,
size: Size<Pixels>,
build_root: impl FnOnce(&mut Window, &mut App) -> Entity<V>,
) -> Result<WindowHandle<V>> {
use crate::{point, px};
let bounds = Bounds {
origin: point(px(0.0), px(0.0)),
size,
};
let mut cx = self.app.borrow_mut();
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
focus: false,
show: false,
..Default::default()
},
build_root,
)
}
pub fn run_until_parked(&self) {
self.dispatcher.run_until_parked();
}
pub fn advance_clock(&self, duration: Duration) {
self.dispatcher.advance_clock(duration);
}
pub fn allow_parking(&self) {
self.dispatcher.allow_parking();
}
pub fn forbid_parking(&self) {
self.dispatcher.forbid_parking();
}
pub fn update<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
let mut app = self.app.borrow_mut();
f(&mut app)
}
pub fn update_window<R>(
&mut self,
window: AnyWindowHandle,
f: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
) -> Result<R> {
let mut app = self.app.borrow_mut();
app.update_window(window, f)
}
pub fn capture_screenshot(&mut self, window: AnyWindowHandle) -> Result<RgbaImage> {
let mut app = self.app.borrow_mut();
app.update_window(window, |_, window, _| window.render_to_image())?
}
pub fn text_system(&self) -> &Arc<TextSystem> {
&self.text_system
}
pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor
}
pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor
}
}
impl Drop for HeadlessAppContext {
fn drop(&mut self) {
self.app.borrow_mut().shutdown();
}
}
impl AppContext for HeadlessAppContext {
fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
let mut app = self.app.borrow_mut();
app.new(build_entity)
}
fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
let mut app = self.app.borrow_mut();
app.reserve_entity()
}
fn insert_entity<T: 'static>(
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<T>) -> T,
) -> Entity<T> {
let mut app = self.app.borrow_mut();
app.insert_entity(reservation, build_entity)
}
fn update_entity<T: 'static, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<T>) -> R,
) -> R {
let mut app = self.app.borrow_mut();
app.update_entity(handle, update)
}
fn as_mut<'a, T>(&'a mut self, _: &Entity<T>) -> GpuiBorrow<'a, T>
where
T: 'static,
{
panic!("Cannot use as_mut with HeadlessAppContext. Call update() instead.")
}
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static,
{
let app = self.app.borrow();
app.read_entity(handle, read)
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
let mut lock = self.app.borrow_mut();
lock.update_window(window, f)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(Entity<T>, &App) -> R,
) -> Result<R>
where
T: 'static,
{
let app = self.app.borrow();
app.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{
let app = self.app.borrow();
app.read_global(callback)
}
}