1use std::num::NonZeroU32;
20use std::ffi::CString;
21use std::time::{Duration, Instant};
22
23use glutin::config::ConfigTemplateBuilder;
24use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentGlContext,
25 PossiblyCurrentContext, Version};
26use glutin::display::{GetGlDisplay, GlDisplay};
27use glutin::surface::{GlSurface, Surface, WindowSurface};
28use glutin_winit::{DisplayBuilder, GlWindow};
29use glow::HasContext;
30use raw_window_handle::HasWindowHandle;
31use winit::dpi::LogicalSize;
32use winit::event::{ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent};
33use winit::event_loop::EventLoop;
34use winit::keyboard::{KeyCode, PhysicalKey};
35use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
36use winit::window::Window;
37use glam::{Mat4, Vec2, Vec3};
38use bytemuck::cast_slice;
39
40use crate::config::{EngineConfig, RenderConfig};
41use crate::scene::Scene;
42use crate::render::camera::ProofCamera;
43use crate::render::postfx::PostFxPipeline;
44use crate::input::{InputState, Key};
45use crate::glyph::atlas::FontAtlas;
46use crate::glyph::batch::GlyphInstance;
47
48const VERT_SRC: &str = r#"
51#version 330 core
52
53layout(location = 0) in vec2 v_pos;
54layout(location = 1) in vec2 v_uv;
55
56layout(location = 2) in vec3 i_position;
57layout(location = 3) in vec2 i_scale;
58layout(location = 4) in float i_rotation;
59layout(location = 5) in vec4 i_color;
60layout(location = 6) in float i_emission;
61layout(location = 7) in vec3 i_glow_color;
62layout(location = 8) in float i_glow_radius;
63layout(location = 9) in vec2 i_uv_offset;
64layout(location = 10) in vec2 i_uv_size;
65
66uniform mat4 u_view_proj;
67
68out vec2 f_uv;
69out vec4 f_color;
70out float f_emission;
71out vec3 f_glow_color;
72out float f_glow_radius;
73
74void main() {
75 float c = cos(i_rotation);
76 float s = sin(i_rotation);
77 vec2 rotated = vec2(
78 v_pos.x * c - v_pos.y * s,
79 v_pos.x * s + v_pos.y * c
80 ) * i_scale;
81
82 gl_Position = u_view_proj * vec4(i_position + vec3(rotated, 0.0), 1.0);
83 gl_Position.y = -gl_Position.y; // FBO renders upside-down relative to screen
84
85 f_uv = i_uv_offset + v_uv * i_uv_size;
86 f_color = i_color;
87 f_emission = i_emission;
88 f_glow_color = i_glow_color;
89 f_glow_radius = i_glow_radius;
90}
91"#;
92
93const FRAG_SRC: &str = r#"
98#version 330 core
99
100in vec2 f_uv;
101in vec4 f_color;
102in float f_emission;
103in vec3 f_glow_color;
104in float f_glow_radius;
105
106uniform sampler2D u_atlas;
107
108layout(location = 0) out vec4 o_color;
109layout(location = 1) out vec4 o_emission;
110
111void main() {
112 float alpha = texture(u_atlas, f_uv).r;
113 if (alpha < 0.05) discard;
114
115 // Base color with emission tint
116 float em = clamp(f_emission * 0.5, 0.0, 1.0);
117 vec3 col = mix(f_color.rgb, f_glow_color, em);
118 o_color = vec4(col, alpha * f_color.a);
119
120 // Emission output: only bright, glowing pixels go to the bloom input.
121 // The bloom amount is proportional to (emission - 0.3), clamped.
122 float bloom_strength = clamp(f_emission - 0.3, 0.0, 1.0);
123 // Add glow radius influence — higher glow_radius means more bloom spread
124 float glow_boost = clamp(f_glow_radius * 0.15, 0.0, 0.8);
125 o_emission = vec4(f_glow_color * (bloom_strength + glow_boost), alpha * f_color.a);
126}
127"#;
128
129#[rustfmt::skip]
133const QUAD_VERTS: [f32; 24] = [
134 -0.5, 0.5, 0.0, 1.0,
135 -0.5, -0.5, 0.0, 0.0,
136 0.5, 0.5, 1.0, 1.0,
137 -0.5, -0.5, 0.0, 0.0,
138 0.5, -0.5, 1.0, 0.0,
139 0.5, 0.5, 1.0, 1.0,
140];
141
142#[derive(Clone, Debug, Default)]
146pub struct FrameStats {
147 pub fps: f32,
149 pub dt: f32,
151 pub glyph_count: usize,
153 pub particle_count: usize,
155 pub draw_calls: u32,
157 pub frame_number: u64,
159}
160
161struct FpsCounter {
163 samples: [f32; 60],
164 head: usize,
165 filled: bool,
166}
167
168impl FpsCounter {
169 fn new() -> Self { Self { samples: [0.016; 60], head: 0, filled: false } }
170
171 fn push(&mut self, dt: f32) {
172 self.samples[self.head] = dt.max(f32::EPSILON);
173 self.head = (self.head + 1) % 60;
174 if self.head == 0 { self.filled = true; }
175 }
176
177 fn fps(&self) -> f32 {
178 let count = if self.filled { 60 } else { self.head.max(1) };
179 let avg_dt: f32 = self.samples[..count].iter().sum::<f32>() / count as f32;
180 1.0 / avg_dt
181 }
182}
183
184#[allow(dead_code)]
192pub struct Pipeline {
193 pub width: u32,
195 pub height: u32,
196 pub stats: FrameStats,
197 running: bool,
198
199 render_config: RenderConfig,
201
202 event_loop: EventLoop<()>,
204 window: Window,
205 surface: Surface<WindowSurface>,
206 context: PossiblyCurrentContext,
207
208 gl: glow::Context,
210 program: glow::Program,
211 vao: glow::VertexArray,
212 quad_vbo: glow::Buffer,
213 instance_vbo: glow::Buffer,
214 atlas_tex: glow::Texture,
215 loc_view_proj: glow::UniformLocation,
216
217 postfx: PostFxPipeline,
219
220 atlas: FontAtlas,
222
223 instances: Vec<GlyphInstance>,
225
226 fps_counter: FpsCounter,
228 frame_start: Instant,
229 scene_time: f32,
230
231 mouse_pos: Vec2,
233 mouse_pos_prev: Vec2,
234 mouse_ndc: Vec2,
236
237 pub raw_window_events: Vec<winit::event::WindowEvent>,
240}
241
242impl Pipeline {
243 pub fn init(config: &EngineConfig) -> Self {
245 let event_loop = EventLoop::new().expect("EventLoop::new");
247
248 let window_attrs = Window::default_attributes()
250 .with_title(&config.window_title)
251 .with_inner_size(LogicalSize::new(config.window_width, config.window_height))
252 .with_resizable(true);
253
254 let template = ConfigTemplateBuilder::new()
256 .with_alpha_size(8)
257 .with_depth_size(0);
258
259 let display_builder = DisplayBuilder::new()
260 .with_window_attributes(Some(window_attrs));
261
262 let (window, gl_config) = display_builder
263 .build(&event_loop, template, |mut configs| {
264 configs.next().expect("no suitable GL config found")
265 })
266 .expect("DisplayBuilder::build failed");
267
268 let window = window.expect("window was not created");
269 let display = gl_config.display();
270
271 let raw_handle = window.window_handle().unwrap().as_raw();
273 let ctx_attrs = ContextAttributesBuilder::new()
274 .with_context_api(ContextApi::OpenGl(Some(Version::new(3, 3))))
275 .build(Some(raw_handle));
276
277 let not_current = unsafe {
278 display.create_context(&gl_config, &ctx_attrs)
279 .expect("create_context failed")
280 };
281
282 let size = window.inner_size();
284 let w = size.width.max(1);
285 let h = size.height.max(1);
286
287 let surface_attrs = window
288 .build_surface_attributes(Default::default())
289 .expect("build_surface_attributes failed");
290
291 let surface = unsafe {
292 display.create_window_surface(&gl_config, &surface_attrs)
293 .expect("create_window_surface failed")
294 };
295
296 let context = not_current.make_current(&surface)
298 .expect("make_current failed");
299
300 let gl = unsafe {
302 glow::Context::from_loader_function(|sym| {
303 let sym_c = CString::new(sym).unwrap();
304 display.get_proc_address(sym_c.as_c_str()) as *const _
305 })
306 };
307
308 let program = unsafe { compile_program(&gl, VERT_SRC, FRAG_SRC) };
310 let loc_view_proj = unsafe {
311 gl.get_uniform_location(program, "u_view_proj")
312 .expect("uniform u_view_proj not found")
313 };
314 unsafe {
315 gl.use_program(Some(program));
316 if let Some(loc) = gl.get_uniform_location(program, "u_atlas") {
317 gl.uniform_1_i32(Some(&loc), 0);
318 }
319 }
320
321 let (vao, quad_vbo, instance_vbo) = unsafe { setup_vao(&gl) };
323
324 let atlas = FontAtlas::build(config.render.font_size as f32);
326 let atlas_tex = unsafe { upload_atlas(&gl, &atlas) };
327
328 let postfx = unsafe { PostFxPipeline::new(&gl, w, h) };
330
331 unsafe {
333 gl.enable(glow::BLEND);
334 gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
335 gl.clear_color(0.02, 0.02, 0.05, 1.0);
336 gl.viewport(0, 0, w as i32, h as i32);
337 }
338
339 log::info!(
340 "Pipeline ready — {}×{} — font atlas {}×{} ({} chars) — PostFxPipeline wired",
341 w, h, atlas.width, atlas.height, atlas.uvs.len()
342 );
343
344 Self {
345 width: w, height: h,
346 stats: FrameStats::default(),
347 running: true,
348 render_config: config.render.clone(),
349 event_loop, window, surface, context,
350 gl, program, vao, quad_vbo, instance_vbo, atlas_tex, loc_view_proj,
351 postfx,
352 atlas,
353 instances: Vec::with_capacity(8192),
354 fps_counter: FpsCounter::new(),
355 frame_start: Instant::now(),
356 scene_time: 0.0,
357 mouse_pos: Vec2::ZERO,
358 mouse_pos_prev: Vec2::ZERO,
359 mouse_ndc: Vec2::ZERO,
360 raw_window_events: Vec::new(),
361 }
362 }
363
364 pub fn update_render_config(&mut self, config: &RenderConfig) {
367 self.render_config = config.clone();
368 }
369
370 pub fn poll_events(&mut self, input: &mut InputState) -> bool {
372 input.clear_frame();
373 self.mouse_pos_prev = self.mouse_pos;
374
375 let mut should_exit = false;
376 let mut resize: Option<(u32, u32)> = None;
377 let mut key_events: Vec<(KeyCode, bool)> = Vec::new();
378 let mut mouse_moved: Option<(f64, f64)> = None;
379 let mut mouse_buttons: Vec<(MouseButton, bool)> = Vec::new();
380 let mut scroll_delta: f32 = 0.0;
381
382 self.raw_window_events.clear();
383
384 #[allow(deprecated)]
385 let status = self.event_loop.pump_events(Some(Duration::ZERO), |event, elwt| {
386 match event {
387 Event::WindowEvent { event: we, .. } => match we {
388 WindowEvent::CloseRequested => {
389 should_exit = true;
390 elwt.exit();
391 }
392 WindowEvent::Resized(s) => {
393 resize = Some((s.width, s.height));
394 }
395 WindowEvent::KeyboardInput { event: key_ev, .. } => {
396 if let PhysicalKey::Code(kc) = key_ev.physical_key {
397 let pressed = key_ev.state == ElementState::Pressed;
398 key_events.push((kc, pressed));
399 }
400 }
401 WindowEvent::CursorMoved { position, .. } => {
402 mouse_moved = Some((position.x, position.y));
403 }
404 WindowEvent::MouseInput { button, state, .. } => {
405 let pressed = state == ElementState::Pressed;
406 mouse_buttons.push((button, pressed));
407 }
408 WindowEvent::MouseWheel { delta, .. } => {
409 scroll_delta += match delta {
410 MouseScrollDelta::LineDelta(_, y) => y,
411 MouseScrollDelta::PixelDelta(d) => d.y as f32 / 40.0,
412 };
413 }
414 _ => {}
415 }
416 _ => {}
417 }
418 });
419
420 if let Some((w, h)) = resize {
422 if w > 0 && h > 0 {
423 self.surface.resize(
424 &self.context,
425 NonZeroU32::new(w).unwrap(),
426 NonZeroU32::new(h).unwrap(),
427 );
428 unsafe { self.gl.viewport(0, 0, w as i32, h as i32); }
429 self.width = w;
430 self.height = h;
431 input.window_resized = Some((w, h));
432 unsafe { self.postfx.resize(&self.gl, w, h); }
433 }
434 }
435
436 for (kc, pressed) in key_events {
438 if let Some(key) = keycode_to_engine(kc) {
439 if pressed {
440 input.keys_pressed.insert(key);
441 input.keys_just_pressed.insert(key);
442 } else {
443 input.keys_pressed.remove(&key);
444 input.keys_just_released.insert(key);
445 }
446 }
447 }
448
449 if let Some((x, y)) = mouse_moved {
451 self.mouse_pos = Vec2::new(x as f32, y as f32);
452 input.mouse_x = x as f32;
453 input.mouse_y = y as f32;
454 let w = self.width.max(1) as f32;
456 let h = self.height.max(1) as f32;
457 self.mouse_ndc = Vec2::new(
458 (x as f32 / w) * 2.0 - 1.0,
459 1.0 - (y as f32 / h) * 2.0,
460 );
461 input.mouse_ndc = self.mouse_ndc;
462 input.mouse_delta = self.mouse_pos - self.mouse_pos_prev;
463 }
464
465 for (button, pressed) in mouse_buttons {
466 match button {
467 MouseButton::Left => {
468 if pressed { input.mouse_left_just_pressed = true; }
469 else { input.mouse_left_just_released = true; }
470 input.mouse_left = pressed;
471 }
472 MouseButton::Right => {
473 if pressed { input.mouse_right_just_pressed = true; }
474 else { input.mouse_right_just_released = true; }
475 input.mouse_right = pressed;
476 }
477 MouseButton::Middle => {
478 if pressed { input.mouse_middle_just_pressed = true; }
479 input.mouse_middle = pressed;
480 }
481 _ => {}
482 }
483 }
484
485 input.scroll_delta = scroll_delta;
486
487 if should_exit || matches!(status, PumpStatus::Exit(_)) {
489 self.running = false;
490 }
491 self.running
492 }
493
494 pub fn render(&mut self, scene: &Scene, camera: &ProofCamera) {
497 let now = Instant::now();
499 let dt = now.duration_since(self.frame_start).as_secs_f32().min(0.1);
500 self.frame_start = now;
501 self.scene_time = scene.time;
502
503 self.fps_counter.push(dt);
504 self.stats.fps = self.fps_counter.fps();
505 self.stats.dt = dt;
506 self.stats.frame_number += 1;
507
508 let pos = camera.position.position();
510 let tgt = camera.target.position();
511 let fov = camera.fov.position;
512 let aspect = if self.height > 0 { self.width as f32 / self.height as f32 } else { 1.0 };
513 let view = Mat4::look_at_rh(pos, tgt, Vec3::Y);
514 let proj = Mat4::perspective_rh_gl(fov.to_radians(), aspect, camera.near, camera.far);
515 let view_proj = proj * view;
516
517 self.instances.clear();
519 let mut glyph_count = 0;
520 let mut particle_count = 0;
521
522 for (_, glyph) in scene.glyphs.iter() {
524 if !glyph.visible { continue; }
525 let life_scale = if let Some(ref f) = glyph.life_function {
526 f.evaluate(scene.time, 0.0)
527 } else {
528 1.0
529 };
530 let uv = self.atlas.uv_for(glyph.character);
531 self.instances.push(GlyphInstance {
532 position: glyph.position.to_array(),
533 scale: [glyph.scale.x * life_scale, glyph.scale.y * life_scale],
534 rotation: glyph.rotation,
535 color: glyph.color.to_array(),
536 emission: glyph.emission,
537 glow_color: glyph.glow_color.to_array(),
538 glow_radius: glyph.glow_radius,
539 uv_offset: uv.offset(),
540 uv_size: uv.size(),
541 _pad: [0.0; 2],
542 });
543 glyph_count += 1;
544 }
545
546 for particle in scene.particles.iter() {
547 let g = &particle.glyph;
548 if !g.visible { continue; }
549 let uv = self.atlas.uv_for(g.character);
550 self.instances.push(GlyphInstance {
551 position: g.position.to_array(),
552 scale: [g.scale.x, g.scale.y],
553 rotation: g.rotation,
554 color: g.color.to_array(),
555 emission: g.emission,
556 glow_color: g.glow_color.to_array(),
557 glow_radius: g.glow_radius,
558 uv_offset: uv.offset(),
559 uv_size: uv.size(),
560 _pad: [0.0; 2],
561 });
562 particle_count += 1;
563 }
564
565 self.stats.glyph_count = glyph_count;
566 self.stats.particle_count = particle_count;
567 self.stats.draw_calls = 0;
568
569 unsafe { self.execute_render_passes(view_proj); }
571 }
572
573 pub fn swap(&mut self) -> bool {
575 if let Err(e) = self.surface.swap_buffers(&self.context) {
576 log::error!("swap_buffers failed: {e}");
577 self.running = false;
578 }
579 self.running
580 }
581
582 pub fn gl(&self) -> &glow::Context {
587 &self.gl
588 }
589
590 pub fn window(&self) -> &Window {
592 &self.window
593 }
594
595 pub fn window_size(&self) -> (u32, u32) {
597 let size = self.window.inner_size();
598 (size.width, size.height)
599 }
600
601 unsafe fn execute_render_passes(&mut self, view_proj: Mat4) {
604 let gl = &self.gl;
605
606 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.postfx.scene_fbo));
611 gl.viewport(0, 0, self.width as i32, self.height as i32);
612 gl.clear(glow::COLOR_BUFFER_BIT);
613
614 if !self.instances.is_empty() {
615 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instance_vbo));
617 gl.buffer_data_u8_slice(
618 glow::ARRAY_BUFFER,
619 cast_slice(self.instances.as_slice()),
620 glow::DYNAMIC_DRAW,
621 );
622
623 gl.use_program(Some(self.program));
625 gl.uniform_matrix_4_f32_slice(
626 Some(&self.loc_view_proj),
627 false,
628 &view_proj.to_cols_array(),
629 );
630 gl.active_texture(glow::TEXTURE0);
631 gl.bind_texture(glow::TEXTURE_2D, Some(self.atlas_tex));
632 gl.bind_vertex_array(Some(self.vao));
633 gl.draw_arrays_instanced(glow::TRIANGLES, 0, 6, self.instances.len() as i32);
634 self.stats.draw_calls += 1;
635 }
636
637 self.postfx.run(gl, &self.render_config, self.width, self.height, self.scene_time);
642 self.stats.draw_calls += 4; }
644}
645
646unsafe fn compile_program(gl: &glow::Context, vert_src: &str, frag_src: &str) -> glow::Program {
650 let vs = gl.create_shader(glow::VERTEX_SHADER).expect("create vertex shader");
651 gl.shader_source(vs, vert_src);
652 gl.compile_shader(vs);
653 if !gl.get_shader_compile_status(vs) {
654 let log = gl.get_shader_info_log(vs);
655 panic!("Vertex shader compile error:\n{log}");
656 }
657
658 let fs = gl.create_shader(glow::FRAGMENT_SHADER).expect("create fragment shader");
659 gl.shader_source(fs, frag_src);
660 gl.compile_shader(fs);
661 if !gl.get_shader_compile_status(fs) {
662 let log = gl.get_shader_info_log(fs);
663 panic!("Fragment shader compile error:\n{log}");
664 }
665
666 let prog = gl.create_program().expect("create shader program");
667 gl.attach_shader(prog, vs);
668 gl.attach_shader(prog, fs);
669 gl.link_program(prog);
670 if !gl.get_program_link_status(prog) {
671 let log = gl.get_program_info_log(prog);
672 panic!("Shader link error:\n{log}");
673 }
674
675 gl.detach_shader(prog, vs);
676 gl.detach_shader(prog, fs);
677 gl.delete_shader(vs);
678 gl.delete_shader(fs);
679 prog
680}
681
682unsafe fn setup_vao(gl: &glow::Context) -> (glow::VertexArray, glow::Buffer, glow::Buffer) {
684 let vao = gl.create_vertex_array().expect("create vao");
685 gl.bind_vertex_array(Some(vao));
686
687 let quad_vbo = gl.create_buffer().expect("create quad_vbo");
689 gl.bind_buffer(glow::ARRAY_BUFFER, Some(quad_vbo));
690 gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, cast_slice(&QUAD_VERTS), glow::STATIC_DRAW);
691 gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 16, 0);
693 gl.enable_vertex_attrib_array(0);
694 gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, 16, 8);
696 gl.enable_vertex_attrib_array(1);
697
698 let instance_vbo = gl.create_buffer().expect("create instance_vbo");
700 gl.bind_buffer(glow::ARRAY_BUFFER, Some(instance_vbo));
701
702 let stride = std::mem::size_of::<GlyphInstance>() as i32;
703
704 macro_rules! inst_attr {
706 ($loc:expr, $count:expr, $off:expr) => {{
707 gl.vertex_attrib_pointer_f32($loc, $count, glow::FLOAT, false, stride, $off);
708 gl.enable_vertex_attrib_array($loc);
709 gl.vertex_attrib_divisor($loc, 1); }};
711 }
712
713 inst_attr!(2, 3, 0); inst_attr!(3, 2, 12); inst_attr!(4, 1, 20); inst_attr!(5, 4, 24); inst_attr!(6, 1, 40); inst_attr!(7, 3, 44); inst_attr!(8, 1, 56); inst_attr!(9, 2, 60); inst_attr!(10, 2, 68); (vao, quad_vbo, instance_vbo)
725}
726
727unsafe fn upload_atlas(gl: &glow::Context, atlas: &FontAtlas) -> glow::Texture {
729 let tex = gl.create_texture().expect("create atlas texture");
730 gl.bind_texture(glow::TEXTURE_2D, Some(tex));
731 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
732 gl.tex_image_2d(
733 glow::TEXTURE_2D, 0, glow::R8 as i32,
734 atlas.width as i32, atlas.height as i32,
735 0, glow::RED, glow::UNSIGNED_BYTE,
736 glow::PixelUnpackData::Slice(Some(&atlas.pixels)),
737 );
738 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32);
739 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32);
740 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
741 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
742 tex
743}
744
745fn keycode_to_engine(kc: KeyCode) -> Option<Key> {
749 Some(match kc {
750 KeyCode::KeyA => Key::A, KeyCode::KeyB => Key::B, KeyCode::KeyC => Key::C,
751 KeyCode::KeyD => Key::D, KeyCode::KeyE => Key::E, KeyCode::KeyF => Key::F,
752 KeyCode::KeyG => Key::G, KeyCode::KeyH => Key::H, KeyCode::KeyI => Key::I,
753 KeyCode::KeyJ => Key::J, KeyCode::KeyK => Key::K, KeyCode::KeyL => Key::L,
754 KeyCode::KeyM => Key::M, KeyCode::KeyN => Key::N, KeyCode::KeyO => Key::O,
755 KeyCode::KeyP => Key::P, KeyCode::KeyQ => Key::Q, KeyCode::KeyR => Key::R,
756 KeyCode::KeyS => Key::S, KeyCode::KeyT => Key::T, KeyCode::KeyU => Key::U,
757 KeyCode::KeyV => Key::V, KeyCode::KeyW => Key::W, KeyCode::KeyX => Key::X,
758 KeyCode::KeyY => Key::Y, KeyCode::KeyZ => Key::Z,
759 KeyCode::Digit1 => Key::Num1, KeyCode::Digit2 => Key::Num2,
760 KeyCode::Digit3 => Key::Num3, KeyCode::Digit4 => Key::Num4,
761 KeyCode::Digit5 => Key::Num5, KeyCode::Digit6 => Key::Num6,
762 KeyCode::Digit7 => Key::Num7, KeyCode::Digit8 => Key::Num8,
763 KeyCode::Digit9 => Key::Num9, KeyCode::Digit0 => Key::Num0,
764 KeyCode::ArrowUp => Key::Up, KeyCode::ArrowDown => Key::Down,
765 KeyCode::ArrowLeft => Key::Left, KeyCode::ArrowRight => Key::Right,
766 KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
767 KeyCode::Escape => Key::Escape,
768 KeyCode::Space => Key::Space,
769 KeyCode::Backspace => Key::Backspace,
770 KeyCode::Tab => Key::Tab,
771 KeyCode::ShiftLeft => Key::LShift, KeyCode::ShiftRight => Key::RShift,
772 KeyCode::ControlLeft => Key::LCtrl, KeyCode::ControlRight => Key::RCtrl,
773 KeyCode::AltLeft => Key::LAlt, KeyCode::AltRight => Key::RAlt,
774 KeyCode::F1 => Key::F1, KeyCode::F2 => Key::F2, KeyCode::F3 => Key::F3,
775 KeyCode::F4 => Key::F4, KeyCode::F5 => Key::F5, KeyCode::F6 => Key::F6,
776 KeyCode::F7 => Key::F7, KeyCode::F8 => Key::F8, KeyCode::F9 => Key::F9,
777 KeyCode::F10 => Key::F10, KeyCode::F11 => Key::F11, KeyCode::F12 => Key::F12,
778 KeyCode::Slash => Key::Slash,
779 KeyCode::Backslash => Key::Backslash,
780 KeyCode::Period => Key::Period,
781 KeyCode::Comma => Key::Comma,
782 KeyCode::Semicolon => Key::Semicolon,
783 KeyCode::Quote => Key::Quote,
784 KeyCode::BracketLeft => Key::LBracket,
785 KeyCode::BracketRight => Key::RBracket,
786 KeyCode::Minus => Key::Minus,
787 KeyCode::Equal => Key::Equals,
788 KeyCode::Backquote => Key::Backtick,
789 KeyCode::PageUp => Key::PageUp,
790 KeyCode::PageDown => Key::PageDown,
791 KeyCode::Home => Key::Home,
792 KeyCode::End => Key::End,
793 KeyCode::Insert => Key::Insert,
794 KeyCode::Delete => Key::Delete,
795 _ => return None,
796 })
797}