#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
#[cfg(feature = "window")]
mod app;
#[cfg(all(feature = "canvas", target_arch = "wasm32"))]
mod canvas;
mod event;
#[cfg(all(
any(feature = "window", feature = "canvas"),
not(any(feature = "vello", feature = "vello-hybrid"))
))]
compile_error!(
"the `window` and `canvas` features need a wgpu rasterising backend: \
enable `vello` (compute shaders) or `vello-hybrid` (sparse strips). \
For a WebGL2 build with no wgpu at all, use `webgl` instead."
);
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
mod renderer;
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
mod surface;
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
mod webgl_host;
#[cfg(all(feature = "canvas", target_arch = "wasm32"))]
pub use canvas::CanvasHost;
pub use event::{Event, MouseButton};
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
pub use renderer::Backend;
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
pub use webgl_host::WebGlHost;
use std::cell::Cell;
use crate::color::Color;
use crate::geometry::{Point, Size};
use crate::scene::SceneBuilder;
const BASE_DPI: f64 = 96.0;
pub trait WindowApp {
fn draw(&mut self, frame: &mut Frame<'_>);
fn event(&mut self, ctx: &mut EventCtx<'_>, event: Event) {
let _ = (ctx, event);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PresentMode {
#[default]
Vsync,
NoVsync,
}
impl PresentMode {
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
fn to_wgpu(self) -> wgpu::PresentMode {
match self {
PresentMode::Vsync => wgpu::PresentMode::AutoVsync,
PresentMode::NoVsync => wgpu::PresentMode::AutoNoVsync,
}
}
}
#[derive(Debug, Clone)]
pub struct WindowConfig {
title: String,
width: u32,
height: u32,
background: Color,
picking: bool,
continuous_redraw: bool,
present_mode: PresentMode,
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
backend: Backend,
pick_interval: Option<std::time::Duration>,
}
impl WindowConfig {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
width: 800,
height: 600,
background: Color::WHITE,
picking: false,
continuous_redraw: false,
present_mode: PresentMode::default(),
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
backend: Backend::default(),
pick_interval: None,
}
}
pub fn size(mut self, width: u32, height: u32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn background(mut self, background: Color) -> Self {
self.background = background;
self
}
pub fn picking(mut self, picking: bool) -> Self {
self.picking = picking;
self
}
pub fn pick_interval(mut self, interval: std::time::Duration) -> Self {
self.pick_interval = Some(interval);
self
}
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = backend;
self
}
#[cfg(any(feature = "vello", feature = "vello-hybrid"))]
pub fn selected_backend(&self) -> Backend {
self.backend
}
pub fn continuous_redraw(mut self, continuous: bool) -> Self {
self.continuous_redraw = continuous;
self
}
pub fn present_mode(mut self, mode: PresentMode) -> Self {
self.present_mode = mode;
self
}
pub fn title(&self) -> &str {
&self.title
}
}
pub struct Frame<'a> {
scene: &'a mut dyn SceneBuilder,
size: Size,
dpi: f64,
}
impl Frame<'_> {
pub fn scene(&mut self) -> &mut dyn SceneBuilder {
self.scene
}
pub fn parts(&mut self) -> (&mut dyn SceneBuilder, Size, f64) {
(self.scene, self.size, self.dpi)
}
pub fn size(&self) -> Size {
self.size
}
pub fn dpi(&self) -> f64 {
self.dpi
}
}
pub(crate) trait PickSource {
fn pick_at(&self, x: u32, y: u32) -> Option<u32>;
}
pub struct EventCtx<'a> {
renderer: &'a dyn PickSource,
redraw: &'a Cell<bool>,
cursor: Option<Point>,
size: Size,
dpi: f64,
exit: &'a mut bool,
}
impl EventCtx<'_> {
pub fn pick_at(&self, x: u32, y: u32) -> Option<u32> {
self.renderer.pick_at(x, y)
}
pub fn cursor(&self) -> Option<Point> {
self.cursor
}
pub fn request_redraw(&self) {
self.redraw.set(true);
}
pub fn exit(&mut self) {
*self.exit = true;
}
pub fn size(&self) -> Size {
self.size
}
pub fn dpi(&self) -> f64 {
self.dpi
}
}
#[derive(Debug, thiserror::Error)]
pub enum WindowError {
#[error(transparent)]
Backend(#[from] crate::BackendError),
#[error("event loop failed: {0}")]
EventLoop(String),
#[error("failed to create window: {0}")]
Window(String),
#[error("surface failed: {0}")]
Surface(String),
#[error("no GPU adapter compatible with the window surface")]
NoAdapter,
#[error("surface offers no non-sRGB 8-bit format")]
UnsupportedSurfaceFormat,
#[error("failed to acquire GPU device: {0}")]
DeviceRequest(String),
}
#[cfg(all(feature = "window", not(target_arch = "wasm32")))]
pub fn run<A: WindowApp>(config: WindowConfig, app: A) -> Result<(), WindowError> {
app::run(config, app)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_defaults_to_an_800_by_600_opaque_window_without_picking() {
let config = WindowConfig::new("demo");
assert_eq!(config.title(), "demo");
assert_eq!((config.width, config.height), (800, 600));
assert_eq!(config.background, Color::WHITE);
assert!(!config.picking);
assert!(!config.continuous_redraw);
assert_eq!(config.present_mode, PresentMode::Vsync);
}
#[test]
fn config_builders_chain() {
let config = WindowConfig::new("demo")
.size(320, 240)
.picking(true)
.continuous_redraw(true)
.present_mode(PresentMode::NoVsync);
assert_eq!((config.width, config.height), (320, 240));
assert!(config.picking);
assert!(config.continuous_redraw);
assert_eq!(config.present_mode, PresentMode::NoVsync);
}
}