1use crate::world::map::WorldMap;
2use crate::{
3 gfx::{Gfx, GfxBuilder, MaybeGfx},
4 gui::EguiRenderer,
5 world::World,
6 ConnectionOnlyOnNative,
7};
8use glam::dvec2;
9use rusqlite::Connection;
10use std::sync::{Arc, Mutex};
11use winit::{
12 application::ApplicationHandler,
13 event::{DeviceEvent, DeviceId, ElementState, KeyEvent, WindowEvent},
14 event_loop::{ActiveEventLoop, EventLoop},
15 keyboard::{KeyCode, PhysicalKey},
16 window::{Fullscreen, Window, WindowAttributes, WindowId},
17};
18
19pub(crate) const WASM_WIN_SIZE: (u32, u32) = (640 * 2, 480 * 2);
20
21// TODO citation:
22// https://github.com/Bentebent/rita/ for winit 29.0->30.0 migration
23// https://github.com/erer1243/wgpu-0.20-winit-0.30-web-example/blob/master/src/lib.rs For better winit 30.0 impl
24// thanks everyone. the migration is really counter-intuitive
25
26pub struct Application {
27 window_attributes: WindowAttributes,
28 gfx_state: MaybeGfx,
29 window: Option<Arc<Window>>,
30 egui: Option<EguiRenderer>,
31 world: Arc<Mutex<World>>,
32 last_render_time: instant::Instant,
33 conn: ConnectionOnlyOnNative,
34}
35
36impl Application {
37 pub fn new(event_loop: &EventLoop<Gfx>, title: &str, conn: Connection) -> Self {
38 let world = Arc::new(Mutex::new(World {
39 map: WorldMap::new(),
40 dirty: false,
41 }));
42 Self {
43 window_attributes: Window::default_attributes().with_title(title),
44 gfx_state: MaybeGfx::Builder(GfxBuilder::new(event_loop.create_proxy())),
45 window: None,
46 egui: None,
47 world: world.clone(),
48 last_render_time: instant::Instant::now(),
49 conn,
50 }
51 }
52}
53
54impl ApplicationHandler<Gfx> for Application {
55 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
56 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
57 let window = Arc::new(
58 event_loop
59 .create_window(self.window_attributes.clone())
60 .unwrap(),
61 );
62
63 if let MaybeGfx::Builder(builder) = &mut self.gfx_state {
64 builder.build_and_send(window.clone());
65 }
66
67 #[cfg(target_arch = "wasm32")]
68 {
69 // Winit prevents sizing with CSS, so we have to set
70 // the size manually when on web.
71 use winit::dpi::PhysicalSize;
72 let _ = window.request_inner_size(PhysicalSize::new(WASM_WIN_SIZE.0, WASM_WIN_SIZE.1));
73
74 use winit::platform::web::WindowExtWebSys;
75 web_sys::window()
76 .and_then(|win| win.document())
77 .and_then(|doc| {
78 let dst = doc.get_element_by_id("wasm-example")?;
79 let canvas = web_sys::Element::from(window.canvas()?);
80 dst.append_child(&canvas).ok()?;
81 Some(())
82 })
83 .expect("Couldn't append canvas to document body.");
84 }
85
86 window.request_redraw();
87
88 self.window = Some(window);
89 }
90
91 fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) {
92 if let (MaybeGfx::Graphics(gfx), DeviceEvent::MouseMotion { delta }) =
93 (&mut self.gfx_state, event)
94 {
95 if gfx.camera.mouse_focused {
96 gfx.camera
97 .controller
98 .process_mouse(dvec2(delta.0, delta.1).as_vec2())
99 }
100 }
101 }
102 fn user_event(&mut self, _event_loop: &ActiveEventLoop, gfx: Gfx) {
103 if let Some(window) = &self.window {
104 let egui = EguiRenderer::new(&gfx.device, gfx.surface_config.format, None, 1, window);
105 self.egui = Some(egui);
106 }
107 self.gfx_state = MaybeGfx::Graphics(gfx);
108 }
109
110 fn window_event(
111 &mut self,
112 event_loop: &ActiveEventLoop,
113 _window_id: WindowId,
114 event: WindowEvent,
115 ) {
116 let MaybeGfx::Graphics(gfx) = &mut self.gfx_state else {
117 if let (WindowEvent::RedrawRequested, Some(ref window)) = (event, &self.window) {
118 window.request_redraw();
119 }
120 return;
121 };
122
123 if let Some(ref window) = &self.window {
124 // Returns true if EGUI consumes the input.
125 if self
126 .egui
127 .as_mut()
128 .map(|egui| egui.handle_input(window, &event))
129 .is_some_and(std::convert::identity)
130 {
131 return;
132 }
133
134 gfx.input(&event, window.inner_size());
135 }
136
137 match event {
138 WindowEvent::CloseRequested => event_loop.exit(),
139
140 WindowEvent::MouseInput {
141 button: winit::event::MouseButton::Left,
142 ..
143 } => {
144 if !gfx.camera.mouse_focused {
145 gfx.camera.mouse_focused = true;
146 if let Some(ref window) = &self.window {
147 match window.set_cursor_grab(winit::window::CursorGrabMode::Locked) {
148 Ok(()) => {
149 window.set_cursor_visible(false);
150 }
151 Err(e) => {
152 log::error!("{e}");
153 }
154 }
155 }
156 }
157 }
158
159 WindowEvent::KeyboardInput { event, .. } => match event {
160 KeyEvent {
161 state: ElementState::Pressed,
162 physical_key: PhysicalKey::Code(KeyCode::KeyX),
163 ..
164 } => event_loop.exit(),
165
166 KeyEvent {
167 state: ElementState::Pressed,
168 physical_key: PhysicalKey::Code(KeyCode::Escape),
169 ..
170 } => {
171 if let Some(ref window) = &self.window {
172 if gfx.camera.mouse_focused {
173 gfx.camera.mouse_focused = false;
174
175 match window.set_cursor_grab(winit::window::CursorGrabMode::None) {
176 Ok(()) => {
177 window.set_cursor_visible(true);
178 }
179 Err(e) => {
180 log::error!("{e}");
181 }
182 }
183 }
184 }
185 }
186
187 KeyEvent {
188 state: ElementState::Pressed,
189 physical_key: PhysicalKey::Code(KeyCode::KeyF),
190 ..
191 } => {
192 if let Some(ref window) = &self.window {
193 let fullscreen = if window.fullscreen().is_some() {
194 None
195 } else {
196 Some(Fullscreen::Borderless(None))
197 };
198
199 window.set_fullscreen(fullscreen);
200 }
201 }
202 _ => {}
203 },
204 WindowEvent::Resized(physical_size) => {
205 gfx.resize(physical_size);
206 }
207 WindowEvent::RedrawRequested => {
208 // Some horrible nesting here! Don't tell Linus...
209 if let Some(ref window) = &self.window {
210 let now = instant::Instant::now();
211 let dt = now - self.last_render_time;
212 self.last_render_time = now;
213
214 // let mut world = self.world.lock().unwrap();
215
216 window.request_redraw();
217 match gfx.render(
218 &mut self.egui,
219 window.clone(),
220 self.world.clone(),
221 &mut self.conn,
222 dt,
223 ) {
224 Ok(_) => {
225 // TODO CITE https://github.com/kaphula/winit-egui-wgpu-template/blob/master/src/app.rs#L3
226 gfx.update(self.world.clone(), &mut self.conn, dt);
227 }
228 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
229 gfx.resize(window.inner_size());
230 }
231 Err(wgpu::SurfaceError::OutOfMemory) => {
232 log::error!("Out of memory!");
233 event_loop.exit();
234 }
235 Err(wgpu::SurfaceError::Timeout) => {
236 log::warn!("Surface timeout!");
237 }
238 Err(wgpu::SurfaceError::Other) => {
239 log::error!("Other surface-error!");
240 event_loop.exit();
241 }
242 }
243 }
244 }
245 _ => {}
246 }
247 }
248}