1use std::usize;
2
3pub use pixels;
4use pixels::{Pixels, SurfaceTexture};
5pub use rgb;
6pub use winit;
7use winit::window::{Window, WindowId};
8
9#[derive(Eq, Hash, PartialEq)]
14pub enum Key {
15 KeyA,
16 KeyZ,
17 KeyE,
18 KeyQ,
19 KeyS,
20 KeyD,
21 KeyW,
22 KeyX,
23 KeyC,
24 Up,
25 Down,
26 Left,
27 Right,
28}
29
30impl std::convert::TryFrom<&winit::keyboard::Key> for Key {
31 type Error = ();
32 fn try_from(value: &winit::keyboard::Key) -> Result<Self, ()> {
33 use winit::keyboard::{Key as WKey, NamedKey as WNamedKey};
34 match value {
35 WKey::Named(WNamedKey::ArrowLeft) => Some(Key::Left),
36 WKey::Named(WNamedKey::ArrowRight) => Some(Key::Right),
37 WKey::Named(WNamedKey::ArrowUp) => Some(Key::Up),
38 WKey::Named(WNamedKey::ArrowDown) => Some(Key::Down),
39 WKey::Character(name) if name == "q" => Some(Key::KeyQ),
40 WKey::Character(name) if name == "d" => Some(Key::KeyD),
41 WKey::Character(name) if name == "z" => Some(Key::KeyZ),
42 WKey::Character(name) if name == "s" => Some(Key::KeyS),
43 WKey::Character(name) if name == "a" => Some(Key::KeyA),
44 WKey::Character(name) if name == "e" => Some(Key::KeyE),
45 WKey::Character(name) if name == "w" => Some(Key::KeyW),
46 WKey::Character(name) if name == "x" => Some(Key::KeyX),
47 WKey::Character(name) if name == "c" => Some(Key::KeyC),
48 _ => None,
49 }
50 .ok_or(())
51 }
52}
53
54pub struct WinitHandler {
57 winfbx: Option<WinFbx>,
58 width: usize,
59 height: usize,
60 last_frame: std::time::Instant,
61 tick: std::time::Duration,
62 always_tick: bool,
66 app: Option<Box<dyn GfxApp>>,
67 cursor_pos: (f64, f64),
68}
69
70fn hz_to_nanosec_period(hz: u16) -> u64 {
71 let nano_period = 1.0 / hz as f64 * 1_000_000_000.0;
72 nano_period as u64
73}
74
75#[cfg(test)]
76mod test {
77 #[test]
78 fn hz_to_nanosec_period() {
79 assert_eq!(super::hz_to_nanosec_period(60), 16_666_666);
80 assert_eq!(super::hz_to_nanosec_period(1), 1_000_000_000);
81 }
82}
83
84impl WinitHandler {
85 pub fn new(app: Box<dyn GfxApp>, size: (usize, usize), tick_per_second: u16) -> Self {
88 let nsec_period = hz_to_nanosec_period(tick_per_second);
89 Self {
90 winfbx: None,
91 width: size.0,
92 height: size.1,
93 last_frame: std::time::Instant::now(),
94 tick: std::time::Duration::from_nanos(nsec_period),
95 app: Some(app),
96 cursor_pos: (0.0, 0.0),
97 always_tick: false,
98 }
99 }
100
101 pub fn run(&mut self) -> Result<(), winit::error::EventLoopError> {
102 let event_loop = winit::event_loop::EventLoop::new()?;
103
104 event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);
106
107 event_loop.run_app(self)?;
108 Ok(())
109 }
110
111 pub fn set_always_tick(&mut self, val: bool) {
115 self.always_tick = val;
116 }
117}
118
119impl winit::application::ApplicationHandler for WinitHandler {
120 fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
122 log::info!(".resumed() called, creating window");
123 if let Some(app) = self.app.take() {
124 self.winfbx = Some(WinFbx::new(event_loop, self.width, self.height, app));
125 }
126 }
127
128 fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
131 let app = self
132 .winfbx
133 .as_mut()
134 .expect("about_to_wait not to be called if window doesn't exist.");
135
136 if app.done() {
137 event_loop.exit();
138 return;
139 }
140 let now = std::time::Instant::now();
141 let duration_from_last_tick = now.duration_since(self.last_frame);
142 if duration_from_last_tick >= self.tick {
148 self.last_frame = now;
149 app.on_tick();
150 app.window.request_redraw();
151 } else {
152 if self.always_tick || !app.pressed_keys.is_empty() {
153 let duration_to_next_tick = self.tick - duration_from_last_tick;
154 event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(
155 now + duration_to_next_tick,
156 ));
157 } else {
158 event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);
159 }
160 }
161 }
162
163 fn window_event(
164 &mut self,
165 event_loop: &winit::event_loop::ActiveEventLoop,
166 _: WindowId,
167 event: winit::event::WindowEvent,
168 ) {
169 let app = self
170 .winfbx
171 .as_mut()
172 .expect("window_event not to be called if window doesn't exist.");
173 use winit::event::WindowEvent;
174 match event {
175 WindowEvent::CloseRequested => {
176 log::info!("The close button was pressed; stopping");
177 event_loop.exit();
178 }
179 WindowEvent::Resized(size) => app.process_resize(size),
180 WindowEvent::KeyboardInput {
181 device_id: _,
182 event,
183 is_synthetic: _,
184 } if event.repeat == false => app.process_kbd_input(event, event_loop),
185 WindowEvent::RedrawRequested => app.on_redraw(),
186 WindowEvent::CursorMoved {
187 device_id: _,
188 position,
189 } => {
190 self.cursor_pos = (position.x, position.y);
191 }
192 WindowEvent::MouseInput {
193 device_id: _,
194 state,
195 button: _,
196 } if state.is_pressed() => {
197 log::info!(
198 "clicked at x: {}, y: {}",
199 self.cursor_pos.0,
200 self.cursor_pos.1
201 )
202 }
203 _ => {}
204 }
205 }
206}
207
208pub fn put_pixel(frame: &mut [u8], width: usize, x: usize, y: usize, color: rgb::RGBA8) {
209 use rgb::*;
210 let idx = width * y + x;
211 frame.as_rgba_mut()[idx] = color;
212}
213
214struct WinFbx {
217 window: Window,
218 pixels: Pixels,
219 pause: bool,
220 height: usize,
221 width: usize,
222 pressed_keys: std::collections::HashSet<Key>,
223 released_keys: std::collections::HashSet<Key>,
224 needs_render: bool,
225 app: Box<dyn GfxApp>,
226}
227
228impl WinFbx {
229 fn new(
230 event_loop: &winit::event_loop::ActiveEventLoop,
231 width: usize,
232 height: usize,
233 app: Box<dyn GfxApp>,
234 ) -> Self {
235 let mut attr = Window::default_attributes();
236 let size = winit::dpi::PhysicalSize::new(width as u16, height as u16);
237 attr = attr.with_inner_size(size).with_title("Box");
238 let win = event_loop.create_window(attr).unwrap();
239
240 let mut pixels = {
241 let surface_texture = SurfaceTexture::new(width as u32, height as u32, &win);
242 Pixels::new(width as u32, height as u32, surface_texture).unwrap()
243 };
244 pixels.clear_color(pixels::wgpu::Color {
245 r: 0.0,
246 g: 0.0,
247 b: 255.0,
248 a: 255.0,
249 });
250 Self {
251 window: win,
252 pixels,
253 height,
254 width,
255 pause: false,
256 pressed_keys: std::collections::HashSet::new(),
257 released_keys: std::collections::HashSet::new(),
258 needs_render: true,
259 app,
260 }
261 }
262
263 fn on_redraw(&mut self) {
264 if self.needs_render {
265 self.app.draw(&mut self.pixels, self.width);
266
267 if let Err(err) = self.pixels.render() {
268 log::error!("failed to render with error {}", err);
269 return;
270 }
271 }
272 self.needs_render = false;
273 }
274
275 fn done(&self) -> bool {
276 self.app.done() == DoneStatus::Exit
277 }
278
279 fn on_tick(&mut self) {
280 if self.app.done() == DoneStatus::NotDone {
281 self.needs_render = self.app.on_tick(&self.pressed_keys);
282 }
283 self.pressed_keys
284 .retain(|candidate| !self.released_keys.contains(candidate));
285 self.released_keys.clear();
286 }
287
288 fn process_kbd_input(
289 &mut self,
290 event: winit::event::KeyEvent,
291 event_loop: &winit::event_loop::ActiveEventLoop,
292 ) {
293 use winit::keyboard::{Key, NamedKey};
294 if let Ok(my_key) = (&event.logical_key).try_into() {
295 if event.state == winit::event::ElementState::Pressed {
296 self.pressed_keys.insert(my_key);
297 } else if event.state == winit::event::ElementState::Released {
298 self.released_keys.insert(my_key);
299 }
300 };
301 if event.state == winit::event::ElementState::Pressed {
302 match event.logical_key {
303 Key::Named(NamedKey::Escape) => event_loop.exit(),
304 Key::Named(NamedKey::Space) => {
305 self.pause = !self.pause;
306 }
307 _ => {}
308 }
309 }
310 }
311
312 fn process_resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
313 self.width = size.width as usize;
314 self.height = size.height as usize;
315 self.pixels.resize_surface(size.width, size.height).unwrap();
316 self.pixels.resize_buffer(size.width, size.height).unwrap();
317 self.window.request_redraw();
318 self.needs_render = true;
319 }
320}
321
322#[derive(Eq, PartialEq)]
323pub enum DoneStatus {
324 Exit,
326 Remain,
330 NotDone,
332}
333
334pub trait GfxApp {
335 fn on_tick(&mut self, pressed_keys: &std::collections::HashSet<Key>) -> bool;
337
338 fn draw(&mut self, pixels: &mut Pixels, width: usize);
340
341 fn done(&self) -> DoneStatus;
344}