use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use winit::dpi::PhysicalSize;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Fullscreen, Window as OsWindow, WindowBuilder},
};
use winit_input_helper::WinitInputHelper;
use crate::ecs::plugin::Plugin;
use crate::rendering::window::{PresentableWindow, WindowConfig, WindowProvider, WindowResource, WindowRunner};
use crate::wgpu::cursor::{CursorGrabMode, CursorIcon};
use crate::wgpu::keycode::{KeyCode, MouseButton};
#[derive(Clone)]
pub struct Input(Arc<Mutex<WinitInputHelper>>);
impl Input {
fn new() -> Self {
Self(Arc::new(Mutex::new(WinitInputHelper::new())))
}
fn update(&self, event: &Event<()>) -> bool {
self.0.lock().unwrap().update(event)
}
pub fn key_pressed(&self, key: KeyCode) -> bool {
self.0.lock().unwrap().key_pressed(key.into())
}
pub fn key_released(&self, key: KeyCode) -> bool {
self.0.lock().unwrap().key_released(key.into())
}
pub fn key_held(&self, key: KeyCode) -> bool {
self.0.lock().unwrap().key_held(key.into())
}
pub fn held_shift(&self) -> bool {
self.0.lock().unwrap().held_shift()
}
pub fn held_control(&self) -> bool {
self.0.lock().unwrap().held_control()
}
pub fn held_alt(&self) -> bool {
self.0.lock().unwrap().held_alt()
}
pub fn mouse_pressed(&self, button: MouseButton) -> bool {
self.0.lock().unwrap().mouse_pressed(button.into())
}
pub fn mouse_released(&self, button: MouseButton) -> bool {
self.0.lock().unwrap().mouse_released(button.into())
}
pub fn mouse_held(&self, button: MouseButton) -> bool {
self.0.lock().unwrap().mouse_held(button.into())
}
pub fn cursor(&self) -> Option<(f32, f32)> {
self.0.lock().unwrap().cursor()
}
pub fn cursor_diff(&self) -> (f32, f32) {
self.0.lock().unwrap().cursor_diff()
}
pub fn mouse_diff(&self) -> (f32, f32) {
self.0.lock().unwrap().mouse_diff()
}
pub fn scroll_diff(&self) -> (f32, f32) {
self.0.lock().unwrap().scroll_diff()
}
pub fn close_requested(&self) -> bool {
self.0.lock().unwrap().close_requested()
}
pub fn resolution(&self) -> Option<(u32, u32)> {
self.0.lock().unwrap().resolution()
}
pub fn dropped_file(&self) -> Option<PathBuf> {
self.0.lock().unwrap().dropped_file()
}
pub fn delta_time(&self) -> Option<Duration> {
self.0.lock().unwrap().delta_time()
}
}
#[derive(Clone)]
pub struct Window(Arc<OsWindow>);
impl Window {
fn new(handle: Arc<OsWindow>) -> Self {
Self(handle)
}
pub fn set_title(&self, title: &str) {
self.0.set_title(title);
}
pub fn inner_size(&self) -> (u32, u32) {
let size = self.0.inner_size();
(size.width, size.height)
}
pub fn set_inner_size(&self, width: u32, height: u32) {
let _ = self.0.request_inner_size(PhysicalSize::new(width, height));
}
pub fn set_min_inner_size(&self, size: Option<(u32, u32)>) {
self.0.set_min_inner_size(size.map(|(w, h)| PhysicalSize::new(w, h)));
}
pub fn set_max_inner_size(&self, size: Option<(u32, u32)>) {
self.0.set_max_inner_size(size.map(|(w, h)| PhysicalSize::new(w, h)));
}
pub fn set_resizable(&self, resizable: bool) {
self.0.set_resizable(resizable);
}
pub fn set_visible(&self, visible: bool) {
self.0.set_visible(visible);
}
pub fn set_minimized(&self, minimized: bool) {
self.0.set_minimized(minimized);
}
pub fn set_maximized(&self, maximized: bool) {
self.0.set_maximized(maximized);
}
pub fn set_decorations(&self, decorations: bool) {
self.0.set_decorations(decorations);
}
pub fn focus(&self) {
self.0.focus_window();
}
pub fn set_fullscreen(&self, fullscreen: bool) {
self.0.set_fullscreen(fullscreen.then_some(Fullscreen::Borderless(None)));
}
pub fn is_fullscreen(&self) -> bool {
self.0.fullscreen().is_some()
}
pub fn set_cursor_icon(&self, icon: CursorIcon) {
self.0.set_cursor_icon(icon.into());
}
pub fn set_cursor_visible(&self, visible: bool) {
self.0.set_cursor_visible(visible);
}
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> bool {
self.0.set_cursor_grab(mode.into()).is_ok()
}
pub fn set_cursor_position(&self, x: f64, y: f64) -> bool {
self.0.set_cursor_position(winit::dpi::PhysicalPosition::new(x, y)).is_ok()
}
pub fn request_redraw(&self) {
self.0.request_redraw();
}
}
pub struct WindowControlPlugin;
impl Plugin for WindowControlPlugin {
fn build(&self, app: &mut crate::prelude::App) {
let handle = app.get_resource::<WindowResource<WinitWindow>>().handle.clone();
app.add_resource(Window::new(handle));
}
}
pub struct WinitWindow {
window: Arc<OsWindow>,
event_loop: EventLoop<()>,
input: Input,
}
impl WindowProvider for WinitWindow {
type Handle = Arc<OsWindow>;
type Exposed = Input;
fn create(config: &WindowConfig) -> Self {
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut window_builder = WindowBuilder::new().with_title(config.title.clone());
#[cfg(not(target_arch = "wasm32"))]
{
window_builder =
window_builder.with_inner_size(PhysicalSize::new(config.width, config.height));
}
#[cfg(target_arch = "wasm32")]
let window = {
use wasm_bindgen::JsCast;
use winit::platform::web::WindowBuilderExtWebSys;
console_error_panic_hook::set_once();
let web_window = web_sys::window().expect("no global `window` exists");
let document = web_window
.document()
.expect("should have a document on window");
let canvas = document
.get_element_by_id("wgpu_canvas")
.expect("no element with id `wgpu_canvas` found — add <canvas id=\"wgpu_canvas\"></canvas> to index.html")
.unchecked_into::<web_sys::HtmlCanvasElement>();
let window = Arc::new(
window_builder
.with_canvas(Some(canvas))
.build(&event_loop)
.unwrap(),
);
let sync_size = {
let window = window.clone();
move || {
let web_window = web_sys::window().expect("no global `window` exists");
let width = web_window.inner_width().unwrap().as_f64().unwrap();
let height = web_window.inner_height().unwrap().as_f64().unwrap();
let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height));
}
};
sync_size();
let closure =
wasm_bindgen::closure::Closure::<dyn FnMut()>::new(sync_size).into_js_value();
web_window
.add_event_listener_with_callback("resize", closure.unchecked_ref())
.expect("failed to add `resize` listener");
window
};
#[cfg(not(target_arch = "wasm32"))]
let window = Arc::new(window_builder.build(&event_loop).unwrap());
Self {
window,
event_loop,
input: Input::new(),
}
}
fn size(handle: &Self::Handle) -> (u32, u32) {
let s = handle.inner_size();
(s.width, s.height)
}
fn exposed(&self) -> Self::Exposed {
self.input.clone()
}
fn handle(&self) -> &Self::Handle {
&self.window
}
}
impl WindowRunner for WinitWindow {
fn run(self, mut on_frame: impl FnMut() + 'static) {
let Self {
window,
event_loop,
input,
} = self;
#[cfg(target_arch = "wasm32")]
window.request_redraw();
event_loop
.run(move |event, elwt| {
let stepped = input.update(&event);
match &event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
#[cfg(target_arch = "wasm32")]
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
on_frame();
window.request_redraw();
}
_ => {}
}
#[cfg(not(target_arch = "wasm32"))]
if stepped {
on_frame();
window.request_redraw();
}
#[cfg(target_arch = "wasm32")]
let _ = stepped;
})
.unwrap();
}
}
impl PresentableWindow for WinitWindow {}