#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
use cranpose_app_shell::FramePacingMode;
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
use std::path::PathBuf;
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
use thiserror::Error;
pub struct AppSettings {
pub window_title: String,
pub initial_width: u32,
pub initial_height: u32,
pub fonts: Option<&'static [&'static [u8]]>,
pub android_use_system_fonts: bool,
pub headless: bool,
pub primary_window_visible: bool,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub dev_options: cranpose_app_shell::DevOptions,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub frame_pacing_mode: FramePacingMode,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu", feature = "robot"))]
pub test_driver: Option<Box<dyn FnOnce(crate::desktop::Robot) + Send + 'static>>,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu", feature = "robot"))]
pub robot_app_hook: Option<Box<crate::RobotAppHook>>,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub record_to: Option<PathBuf>,
}
impl Default for AppSettings {
fn default() -> Self {
Self {
window_title: "Compose App".into(),
initial_width: 800,
initial_height: 600,
fonts: None,
android_use_system_fonts: false,
headless: false,
primary_window_visible: true,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
dev_options: cranpose_app_shell::DevOptions::default(),
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
frame_pacing_mode: FramePacingMode::NoVsync,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu", feature = "robot"))]
test_driver: None,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu", feature = "robot"))]
robot_app_hook: None,
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
record_to: None,
}
}
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
#[derive(Debug, Error)]
pub enum LaunchError {
#[error("failed to create desktop event loop: {0}")]
EventLoopCreate(#[source] winit::error::EventLoopError),
#[error("failed to create desktop window: {0}")]
WindowCreate(#[source] winit::error::RequestError),
#[error("failed to create desktop rendering surface: {0}")]
SurfaceCreate(#[source] wgpu::CreateSurfaceError),
#[error("no compatible GPU adapter was available: {0}")]
NoAdapter(#[source] wgpu::RequestAdapterError),
#[error("failed to create GPU device: {0}")]
DeviceCreate(#[source] wgpu::RequestDeviceError),
#[error("desktop event loop terminated with error: {0}")]
EventLoopRun(#[source] winit::error::EventLoopError),
#[cfg(feature = "robot")]
#[error("desktop robot test driver panicked: {0}")]
TestDriverPanic(String),
}
pub struct AppLauncher {
settings: AppSettings,
}
impl AppLauncher {
pub fn new() -> Self {
Self {
settings: AppSettings::default(),
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.settings.window_title = title.into();
self
}
pub fn with_size(mut self, width: u32, height: u32) -> Self {
self.settings.initial_width = width;
self.settings.initial_height = height;
self
}
pub fn with_fonts(mut self, fonts: &'static [&'static [u8]]) -> Self {
self.settings.fonts = Some(fonts);
self
}
pub fn with_android_use_system_fonts(mut self, use_system_fonts: bool) -> Self {
self.settings.android_use_system_fonts = use_system_fonts;
self
}
pub fn with_headless(mut self, headless: bool) -> Self {
self.settings.headless = headless;
self
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub fn with_fps_counter(mut self, enabled: bool) -> Self {
self.settings.dev_options.fps_counter = enabled;
self
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub fn with_frame_pacing_mode(mut self, mode: FramePacingMode) -> Self {
self.settings.frame_pacing_mode = mode;
self.settings.dev_options.frame_pacing_mode = mode;
self
}
#[cfg(not(all(feature = "desktop", feature = "renderer-wgpu")))]
pub fn with_frame_pacing_mode(self, mode: cranpose_app_shell::FramePacingMode) -> Self {
let _ = mode;
self
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub fn with_frame_pacing_controls(mut self, enabled: bool) -> Self {
self.settings.dev_options.frame_pacing_controls = enabled;
if enabled {
self.settings.dev_options.fps_counter = true;
}
self
}
#[cfg(not(all(feature = "desktop", feature = "renderer-wgpu")))]
pub fn with_frame_pacing_controls(self, enabled: bool) -> Self {
let _ = enabled;
self
}
#[cfg(not(all(feature = "desktop", feature = "renderer-wgpu")))]
pub fn with_fps_counter(self, enabled: bool) -> Self {
let _ = enabled;
self
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu"))]
pub fn with_recording(mut self, path: impl Into<PathBuf>) -> Self {
self.settings.record_to = Some(path.into());
self
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu", feature = "robot"))]
pub fn with_test_driver(
mut self,
driver: impl FnOnce(crate::desktop::Robot) + Send + 'static,
) -> Self {
self.settings.test_driver = Some(Box::new(driver));
self
}
#[cfg(all(feature = "desktop", feature = "renderer-wgpu", feature = "robot"))]
#[doc(hidden)]
pub fn with_robot_app_hook(
mut self,
hook: impl FnMut(String, String) -> Result<Option<String>, String> + 'static,
) -> Self {
self.settings.robot_app_hook = Some(Box::new(hook));
self
}
#[cfg(all(
feature = "desktop",
feature = "renderer-wgpu",
not(target_os = "android")
))]
pub fn try_run(self, content: impl FnMut() + 'static) -> Result<(), LaunchError> {
let mut content = content;
crate::desktop::try_run(self.settings, move || {
crate::ProvideUriHandler(|| {
content();
});
})
}
#[cfg(all(
feature = "desktop",
feature = "renderer-wgpu",
not(target_os = "android")
))]
pub fn run(self, content: impl FnMut() + 'static) -> ! {
self.try_run(content)
.unwrap_or_else(|error| panic!("desktop launch failed: {error}"));
std::process::exit(0)
}
#[cfg(all(
feature = "desktop",
feature = "renderer-wgpu",
not(target_os = "android")
))]
pub fn try_run_windows(mut self, content: impl FnMut() + 'static) -> Result<(), LaunchError> {
self.settings.primary_window_visible = false;
self.try_run(content)
}
#[cfg(all(
feature = "desktop",
feature = "renderer-wgpu",
not(target_os = "android")
))]
pub fn run_windows(self, content: impl FnMut() + 'static) -> ! {
self.try_run_windows(content)
.unwrap_or_else(|error| panic!("desktop launch failed: {error}"));
std::process::exit(0)
}
#[cfg(all(feature = "android", target_os = "android"))]
pub fn run(self, app: android_activity::AndroidApp, content: impl FnMut() + 'static) {
let mut content = content;
crate::android::run(app, self.settings, move || {
crate::ProvideUriHandler(|| {
content();
});
});
}
#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
pub async fn run_web(
self,
canvas_id: &str,
content: impl FnMut() + 'static,
) -> Result<(), wasm_bindgen::JsValue> {
let mut content = content;
crate::web::run(canvas_id, self.settings, move || {
crate::ProvideUriHandler(|| {
content();
});
})
.await
}
}
impl Default for AppLauncher {
fn default() -> Self {
Self::new()
}
}