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::{Fullscreen, Window as OsWindow, WindowBuilder},
14};
15use winit_input_helper::WinitInputHelper;
16
17use crate::ecs::plugin::Plugin;
18use crate::rendering::window::{PresentableWindow, WindowConfig, WindowProvider, WindowResource, WindowRunner};
19use crate::wgpu::cursor::{CursorGrabMode, CursorIcon};
20use crate::wgpu::keycode::{KeyCode, MouseButton};
21
22#[derive(Clone)]
33pub struct Input(Arc<Mutex<WinitInputHelper>>);
34
35impl Input {
36 fn new() -> Self {
37 Self(Arc::new(Mutex::new(WinitInputHelper::new())))
38 }
39
40 fn update(&self, event: &Event<()>) -> bool {
41 self.0.lock().unwrap().update(event)
42 }
43
44 pub fn key_pressed(&self, key: KeyCode) -> bool {
48 self.0.lock().unwrap().key_pressed(key.into())
49 }
50
51 pub fn key_released(&self, key: KeyCode) -> bool {
53 self.0.lock().unwrap().key_released(key.into())
54 }
55
56 pub fn key_held(&self, key: KeyCode) -> bool {
58 self.0.lock().unwrap().key_held(key.into())
59 }
60
61 pub fn held_shift(&self) -> bool {
63 self.0.lock().unwrap().held_shift()
64 }
65
66 pub fn held_control(&self) -> bool {
68 self.0.lock().unwrap().held_control()
69 }
70
71 pub fn held_alt(&self) -> bool {
73 self.0.lock().unwrap().held_alt()
74 }
75
76 pub fn mouse_pressed(&self, button: MouseButton) -> bool {
78 self.0.lock().unwrap().mouse_pressed(button.into())
79 }
80
81 pub fn mouse_released(&self, button: MouseButton) -> bool {
83 self.0.lock().unwrap().mouse_released(button.into())
84 }
85
86 pub fn mouse_held(&self, button: MouseButton) -> bool {
88 self.0.lock().unwrap().mouse_held(button.into())
89 }
90
91 pub fn cursor(&self) -> Option<(f32, f32)> {
94 self.0.lock().unwrap().cursor()
95 }
96
97 pub fn cursor_diff(&self) -> (f32, f32) {
100 self.0.lock().unwrap().cursor_diff()
101 }
102
103 pub fn mouse_diff(&self) -> (f32, f32) {
107 self.0.lock().unwrap().mouse_diff()
108 }
109
110 pub fn scroll_diff(&self) -> (f32, f32) {
112 self.0.lock().unwrap().scroll_diff()
113 }
114
115 pub fn close_requested(&self) -> bool {
118 self.0.lock().unwrap().close_requested()
119 }
120
121 pub fn resolution(&self) -> Option<(u32, u32)> {
123 self.0.lock().unwrap().resolution()
124 }
125
126 pub fn dropped_file(&self) -> Option<PathBuf> {
128 self.0.lock().unwrap().dropped_file()
129 }
130
131 pub fn delta_time(&self) -> Option<Duration> {
134 self.0.lock().unwrap().delta_time()
135 }
136}
137
138#[derive(Clone)]
147pub struct Window(Arc<OsWindow>);
148
149impl Window {
150 fn new(handle: Arc<OsWindow>) -> Self {
151 Self(handle)
152 }
153
154 pub fn set_title(&self, title: &str) {
156 self.0.set_title(title);
157 }
158
159 pub fn inner_size(&self) -> (u32, u32) {
161 let size = self.0.inner_size();
162 (size.width, size.height)
163 }
164
165 pub fn set_inner_size(&self, width: u32, height: u32) {
169 let _ = self.0.request_inner_size(PhysicalSize::new(width, height));
170 }
171
172 pub fn set_min_inner_size(&self, size: Option<(u32, u32)>) {
174 self.0.set_min_inner_size(size.map(|(w, h)| PhysicalSize::new(w, h)));
175 }
176
177 pub fn set_max_inner_size(&self, size: Option<(u32, u32)>) {
179 self.0.set_max_inner_size(size.map(|(w, h)| PhysicalSize::new(w, h)));
180 }
181
182 pub fn set_resizable(&self, resizable: bool) {
184 self.0.set_resizable(resizable);
185 }
186
187 pub fn set_visible(&self, visible: bool) {
189 self.0.set_visible(visible);
190 }
191
192 pub fn set_minimized(&self, minimized: bool) {
194 self.0.set_minimized(minimized);
195 }
196
197 pub fn set_maximized(&self, maximized: bool) {
199 self.0.set_maximized(maximized);
200 }
201
202 pub fn set_decorations(&self, decorations: bool) {
204 self.0.set_decorations(decorations);
205 }
206
207 pub fn focus(&self) {
209 self.0.focus_window();
210 }
211
212 pub fn set_fullscreen(&self, fullscreen: bool) {
215 self.0.set_fullscreen(fullscreen.then_some(Fullscreen::Borderless(None)));
216 }
217
218 pub fn is_fullscreen(&self) -> bool {
220 self.0.fullscreen().is_some()
221 }
222
223 pub fn set_cursor_icon(&self, icon: CursorIcon) {
225 self.0.set_cursor_icon(icon.into());
226 }
227
228 pub fn set_cursor_visible(&self, visible: bool) {
230 self.0.set_cursor_visible(visible);
231 }
232
233 pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> bool {
238 self.0.set_cursor_grab(mode.into()).is_ok()
239 }
240
241 pub fn set_cursor_position(&self, x: f64, y: f64) -> bool {
245 self.0.set_cursor_position(winit::dpi::PhysicalPosition::new(x, y)).is_ok()
246 }
247
248 pub fn request_redraw(&self) {
252 self.0.request_redraw();
253 }
254}
255
256pub struct WindowControlPlugin;
262
263impl Plugin for WindowControlPlugin {
264 fn build(&self, app: &mut crate::prelude::App) {
265 let handle = app.get_resource::<WindowResource<WinitWindow>>().handle.clone();
266 app.add_resource(Window::new(handle));
267 }
268}
269
270pub struct WinitWindow {
271 window: Arc<OsWindow>,
272 event_loop: EventLoop<()>,
273 input: Input,
274}
275
276impl WindowProvider for WinitWindow {
277 type Handle = Arc<OsWindow>;
278 type Exposed = Input;
279
280 fn create(config: &WindowConfig) -> Self {
281 let event_loop = EventLoop::new().unwrap();
282 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
283
284 #[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
285 let mut window_builder = WindowBuilder::new().with_title(config.title.clone());
286
287 #[cfg(not(target_arch = "wasm32"))]
288 {
289 window_builder =
290 window_builder.with_inner_size(PhysicalSize::new(config.width, config.height));
291 }
292
293 #[cfg(target_arch = "wasm32")]
294 let window = {
295 use wasm_bindgen::JsCast;
296 use winit::platform::web::WindowBuilderExtWebSys;
297
298 console_error_panic_hook::set_once();
306
307 let web_window = web_sys::window().expect("no global `window` exists");
308 let document = web_window
309 .document()
310 .expect("should have a document on window");
311 let canvas = document
312 .get_element_by_id("wgpu_canvas")
313 .expect("no element with id `wgpu_canvas` found — add <canvas id=\"wgpu_canvas\"></canvas> to index.html")
314 .unchecked_into::<web_sys::HtmlCanvasElement>();
315
316 let window = Arc::new(
317 window_builder
318 .with_canvas(Some(canvas))
319 .build(&event_loop)
320 .unwrap(),
321 );
322
323 let sync_size = {
328 let window = window.clone();
329 move || {
330 let web_window = web_sys::window().expect("no global `window` exists");
331 let width = web_window.inner_width().unwrap().as_f64().unwrap();
332 let height = web_window.inner_height().unwrap().as_f64().unwrap();
333 let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height));
334 }
335 };
336 sync_size();
337
338 let closure =
339 wasm_bindgen::closure::Closure::<dyn FnMut()>::new(sync_size).into_js_value();
340 web_window
341 .add_event_listener_with_callback("resize", closure.unchecked_ref())
342 .expect("failed to add `resize` listener");
343
344 window
345 };
346
347 #[cfg(not(target_arch = "wasm32"))]
348 let window = Arc::new(window_builder.build(&event_loop).unwrap());
349
350 Self {
351 window,
352 event_loop,
353 input: Input::new(),
354 }
355 }
356
357 fn size(handle: &Self::Handle) -> (u32, u32) {
358 let s = handle.inner_size();
359 (s.width, s.height)
360 }
361
362 fn exposed(&self) -> Self::Exposed {
363 self.input.clone()
364 }
365
366 fn handle(&self) -> &Self::Handle {
367 &self.window
368 }
369}
370
371impl WindowRunner for WinitWindow {
372 fn run(self, mut on_frame: impl FnMut() + 'static) {
373 let Self {
374 window,
375 event_loop,
376 input,
377 } = self;
378
379 #[cfg(target_arch = "wasm32")]
387 window.request_redraw();
388
389 event_loop
390 .run(move |event, elwt| {
391 let stepped = input.update(&event);
392
393 match &event {
394 Event::WindowEvent {
395 event: WindowEvent::CloseRequested,
396 ..
397 } => elwt.exit(),
398 #[cfg(target_arch = "wasm32")]
399 Event::WindowEvent {
400 event: WindowEvent::RedrawRequested,
401 ..
402 } => {
403 on_frame();
404 window.request_redraw();
405 }
406 _ => {}
407 }
408
409 #[cfg(not(target_arch = "wasm32"))]
410 if stepped {
411 on_frame();
412 window.request_redraw();
413 }
414 #[cfg(target_arch = "wasm32")]
415 let _ = stepped;
416 })
417 .unwrap();
418 }
419}
420
421impl PresentableWindow for WinitWindow {}