use std::cell::Cell;
use crate::color::Color;
use crate::geometry::{Point, Size};
use crate::window::renderer::HostRenderer;
use crate::window::surface::WindowSurface;
use crate::window::PickSource as _;
use crate::window::BASE_DPI;
use crate::window::{Event, EventCtx, Frame, WindowApp, WindowConfig, WindowError};
pub struct CanvasHost {
surface: WindowSurface,
renderer: HostRenderer,
background: Color,
dpi: f64,
cursor: Option<Point>,
}
impl CanvasHost {
pub async fn new(
canvas: web_sys::HtmlCanvasElement,
config: WindowConfig,
) -> Result<Self, WindowError> {
let (width, height) = match (canvas.width(), canvas.height()) {
(0, _) | (_, 0) => {
canvas.set_width(config.width);
canvas.set_height(config.height);
(config.width, config.height)
}
wh => wh,
};
let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
descriptor.backends = wgpu::Backends::BROWSER_WEBGPU;
let instance = wgpu::Instance::new(descriptor);
let target = instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
.map_err(|e| WindowError::Surface(e.to_string()))?;
let surface = WindowSurface::new_async(
&instance,
target,
width,
height,
config.present_mode,
(!config.backend.can_present_directly()).then(|| config.backend.target_usage()),
)
.await?;
let mut renderer = HostRenderer::new(
config.backend,
surface.device(),
surface.queue(),
config.picking,
)?;
if config.backend.can_present_directly() {
renderer.set_target_format(surface.format());
}
let dpi = BASE_DPI * device_pixel_ratio();
Ok(Self {
surface,
renderer,
background: config.background,
dpi,
cursor: None,
})
}
pub fn render<A: WindowApp>(&mut self, app: &mut A) -> Result<(), WindowError> {
let (width, height) = self.surface.size();
let size = Size::new(width as f64, height as f64);
self.renderer.scene().clear();
{
let mut frame = Frame {
scene: self.renderer.scene(),
size,
dpi: self.dpi,
};
app.draw(&mut frame);
}
let renderer = &mut self.renderer;
let background = self.background;
self.surface.draw_frame(
|view| {
renderer
.render_to_texture_deferring_pick(view, width, height, background)
.map_err(WindowError::from)
},
|| {},
)?;
Ok(())
}
pub fn dispatch<A: WindowApp>(&mut self, app: &mut A, event: Event) -> bool {
match event {
Event::CursorMoved { position } => self.cursor = Some(position),
Event::CursorLeft => self.cursor = None,
_ => {}
}
let _ = self.renderer.try_finish_pick();
let (width, height) = self.surface.size();
let redraw = Cell::new(false);
let mut exit = false;
let mut ctx = EventCtx {
renderer: &self.renderer,
redraw: &redraw,
cursor: self.cursor,
size: Size::new(width as f64, height as f64),
dpi: self.dpi,
exit: &mut exit,
};
app.event(&mut ctx, event);
redraw.get()
}
pub fn resize(&mut self, width: u32, height: u32, dpi: f64) {
self.surface.resize(width, height);
self.dpi = dpi;
}
pub fn pick_at(&mut self, x: u32, y: u32) -> Option<u32> {
let _ = self.renderer.try_finish_pick();
self.renderer.pick_at(x, y)
}
pub fn size(&self) -> Size {
let (width, height) = self.surface.size();
Size::new(width as f64, height as f64)
}
pub fn dpi(&self) -> f64 {
self.dpi
}
pub fn set_background(&mut self, background: Color) {
self.background = background;
}
}
fn device_pixel_ratio() -> f64 {
web_sys::window()
.map(|w| w.device_pixel_ratio())
.filter(|r| *r > 0.0)
.unwrap_or(1.0)
}