1use std::path::PathBuf;
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5#[cfg(not(target_arch = "wasm32"))]
6use winit::dpi::PhysicalSize;
7use winit::{
8 event::{Event, WindowEvent},
9 event_loop::EventLoop,
10 window::{Window, WindowBuilder},
11};
12use winit_input_helper::WinitInputHelper;
13
14use crate::rendering::window::{PresentableWindow, WindowConfig, WindowProvider, WindowRunner};
15use crate::wgpu::keycode::{KeyCode, MouseButton};
16
17#[derive(Clone)]
28pub struct Input(Arc<Mutex<WinitInputHelper>>);
29
30impl Input {
31 fn new() -> Self {
32 Self(Arc::new(Mutex::new(WinitInputHelper::new())))
33 }
34
35 fn update(&self, event: &Event<()>) -> bool {
36 self.0.lock().unwrap().update(event)
37 }
38
39 pub fn key_pressed(&self, key: KeyCode) -> bool {
43 self.0.lock().unwrap().key_pressed(key.into())
44 }
45
46 pub fn key_released(&self, key: KeyCode) -> bool {
48 self.0.lock().unwrap().key_released(key.into())
49 }
50
51 pub fn key_held(&self, key: KeyCode) -> bool {
53 self.0.lock().unwrap().key_held(key.into())
54 }
55
56 pub fn held_shift(&self) -> bool {
58 self.0.lock().unwrap().held_shift()
59 }
60
61 pub fn held_control(&self) -> bool {
63 self.0.lock().unwrap().held_control()
64 }
65
66 pub fn held_alt(&self) -> bool {
68 self.0.lock().unwrap().held_alt()
69 }
70
71 pub fn mouse_pressed(&self, button: MouseButton) -> bool {
73 self.0.lock().unwrap().mouse_pressed(button.into())
74 }
75
76 pub fn mouse_released(&self, button: MouseButton) -> bool {
78 self.0.lock().unwrap().mouse_released(button.into())
79 }
80
81 pub fn mouse_held(&self, button: MouseButton) -> bool {
83 self.0.lock().unwrap().mouse_held(button.into())
84 }
85
86 pub fn cursor(&self) -> Option<(f32, f32)> {
89 self.0.lock().unwrap().cursor()
90 }
91
92 pub fn cursor_diff(&self) -> (f32, f32) {
95 self.0.lock().unwrap().cursor_diff()
96 }
97
98 pub fn mouse_diff(&self) -> (f32, f32) {
102 self.0.lock().unwrap().mouse_diff()
103 }
104
105 pub fn scroll_diff(&self) -> (f32, f32) {
107 self.0.lock().unwrap().scroll_diff()
108 }
109
110 pub fn close_requested(&self) -> bool {
113 self.0.lock().unwrap().close_requested()
114 }
115
116 pub fn resolution(&self) -> Option<(u32, u32)> {
118 self.0.lock().unwrap().resolution()
119 }
120
121 pub fn dropped_file(&self) -> Option<PathBuf> {
123 self.0.lock().unwrap().dropped_file()
124 }
125
126 pub fn delta_time(&self) -> Option<Duration> {
129 self.0.lock().unwrap().delta_time()
130 }
131}
132
133pub struct WinitWindow {
134 window: Arc<Window>,
135 event_loop: EventLoop<()>,
136 input: Input,
137}
138
139impl WindowProvider for WinitWindow {
140 type Handle = Arc<Window>;
141 type Exposed = Input;
142
143 fn create(config: &WindowConfig) -> Self {
144 let event_loop = EventLoop::new().unwrap();
145 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
146
147 #[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
148 let mut window_builder = WindowBuilder::new().with_title(config.title.clone());
149
150 #[cfg(not(target_arch = "wasm32"))]
151 {
152 window_builder =
153 window_builder.with_inner_size(PhysicalSize::new(config.width, config.height));
154 }
155
156 #[cfg(target_arch = "wasm32")]
157 let window = {
158 use wasm_bindgen::JsCast;
159 use winit::platform::web::WindowBuilderExtWebSys;
160
161 console_error_panic_hook::set_once();
169
170 let web_window = web_sys::window().expect("no global `window` exists");
171 let document = web_window
172 .document()
173 .expect("should have a document on window");
174 let canvas = document
175 .get_element_by_id("wgpu_canvas")
176 .expect("no element with id `wgpu_canvas` found — add <canvas id=\"wgpu_canvas\"></canvas> to index.html")
177 .unchecked_into::<web_sys::HtmlCanvasElement>();
178
179 let window = Arc::new(
180 window_builder
181 .with_canvas(Some(canvas))
182 .build(&event_loop)
183 .unwrap(),
184 );
185
186 let sync_size = {
191 let window = window.clone();
192 move || {
193 let web_window = web_sys::window().expect("no global `window` exists");
194 let width = web_window.inner_width().unwrap().as_f64().unwrap();
195 let height = web_window.inner_height().unwrap().as_f64().unwrap();
196 let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height));
197 }
198 };
199 sync_size();
200
201 let closure =
202 wasm_bindgen::closure::Closure::<dyn FnMut()>::new(sync_size).into_js_value();
203 web_window
204 .add_event_listener_with_callback("resize", closure.unchecked_ref())
205 .expect("failed to add `resize` listener");
206
207 window
208 };
209
210 #[cfg(not(target_arch = "wasm32"))]
211 let window = Arc::new(window_builder.build(&event_loop).unwrap());
212
213 Self {
214 window,
215 event_loop,
216 input: Input::new(),
217 }
218 }
219
220 fn size(handle: &Self::Handle) -> (u32, u32) {
221 let s = handle.inner_size();
222 (s.width, s.height)
223 }
224
225 fn exposed(&self) -> Self::Exposed {
226 self.input.clone()
227 }
228
229 fn handle(&self) -> &Self::Handle {
230 &self.window
231 }
232}
233
234impl WindowRunner for WinitWindow {
235 fn run(self, mut on_frame: impl FnMut() + 'static) {
236 let Self {
237 window,
238 event_loop,
239 input,
240 } = self;
241
242 #[cfg(target_arch = "wasm32")]
250 window.request_redraw();
251
252 event_loop
253 .run(move |event, elwt| {
254 let stepped = input.update(&event);
255
256 match &event {
257 Event::WindowEvent {
258 event: WindowEvent::CloseRequested,
259 ..
260 } => elwt.exit(),
261 #[cfg(target_arch = "wasm32")]
262 Event::WindowEvent {
263 event: WindowEvent::RedrawRequested,
264 ..
265 } => {
266 on_frame();
267 window.request_redraw();
268 }
269 _ => {}
270 }
271
272 #[cfg(not(target_arch = "wasm32"))]
273 if stepped {
274 on_frame();
275 window.request_redraw();
276 }
277 #[cfg(target_arch = "wasm32")]
278 let _ = stepped;
279 })
280 .unwrap();
281 }
282}
283
284impl PresentableWindow for WinitWindow {}