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
84 f_uv = i_uv_offset + v_uv * i_uv_size;
85 f_color = i_color;
86 f_emission = i_emission;
87 f_glow_color = i_glow_color;
88 f_glow_radius = i_glow_radius;
89}
90"#;
91
92const FRAG_SRC: &str = r#"
97#version 330 core
98
99in vec2 f_uv;
100in vec4 f_color;
101in float f_emission;
102in vec3 f_glow_color;
103in float f_glow_radius;
104
105uniform sampler2D u_atlas;
106
107layout(location = 0) out vec4 o_color;
108layout(location = 1) out vec4 o_emission;
109
110void main() {
111 float alpha = texture(u_atlas, f_uv).r;
112 if (alpha < 0.05) discard;
113
114 // Base color with emission tint
115 float em = clamp(f_emission * 0.5, 0.0, 1.0);
116 vec3 col = mix(f_color.rgb, f_glow_color, em);
117 o_color = vec4(col, alpha * f_color.a);
118
119 // Emission output: only bright, glowing pixels go to the bloom input.
120 // The bloom amount is proportional to (emission - 0.3), clamped.
121 float bloom_strength = clamp(f_emission - 0.3, 0.0, 1.0);
122 // Add glow radius influence — higher glow_radius means more bloom spread
123 float glow_boost = clamp(f_glow_radius * 0.15, 0.0, 0.8);
124 o_emission = vec4(f_glow_color * (bloom_strength + glow_boost), alpha * f_color.a);
125}
126"#;
127
128#[rustfmt::skip]
132const QUAD_VERTS: [f32; 24] = [
133 -0.5, 0.5, 0.0, 1.0,
134 -0.5, -0.5, 0.0, 0.0,
135 0.5, 0.5, 1.0, 1.0,
136 -0.5, -0.5, 0.0, 0.0,
137 0.5, -0.5, 1.0, 0.0,
138 0.5, 0.5, 1.0, 1.0,
139];
140
141#[derive(Clone, Debug, Default)]
145pub struct FrameStats {
146 pub fps: f32,
148 pub dt: f32,
150 pub glyph_count: usize,
152 pub particle_count: usize,
154 pub draw_calls: u32,
156 pub frame_number: u64,
158}
159
160struct FpsCounter {
162 samples: [f32; 60],
163 head: usize,
164 filled: bool,
165}
166
167impl FpsCounter {
168 fn new() -> Self { Self { samples: [0.016; 60], head: 0, filled: false } }
169
170 fn push(&mut self, dt: f32) {
171 self.samples[self.head] = dt.max(f32::EPSILON);
172 self.head = (self.head + 1) % 60;
173 if self.head == 0 { self.filled = true; }
174 }
175
176 fn fps(&self) -> f32 {
177 let count = if self.filled { 60 } else { self.head.max(1) };
178 let avg_dt: f32 = self.samples[..count].iter().sum::<f32>() / count as f32;
179 1.0 / avg_dt
180 }
181}
182
183#[allow(dead_code)]
191pub struct Pipeline {
192 pub width: u32,
194 pub height: u32,
195 pub stats: FrameStats,
196 running: bool,
197
198 render_config: RenderConfig,
200
201 event_loop: EventLoop<()>,
203 window: Window,
204 surface: Surface<WindowSurface>,
205 context: PossiblyCurrentContext,
206
207 gl: glow::Context,
209 program: glow::Program,
210 vao: glow::VertexArray,
211 quad_vbo: glow::Buffer,
212 instance_vbo: glow::Buffer,
213 atlas_tex: glow::Texture,
214 loc_view_proj: glow::UniformLocation,
215
216 postfx: PostFxPipeline,
218
219 atlas: FontAtlas,
221
222 instances: Vec<GlyphInstance>,
224
225 fps_counter: FpsCounter,
227 frame_start: Instant,
228 scene_time: f32,
229
230 mouse_pos: Vec2,
232 mouse_pos_prev: Vec2,
233 mouse_ndc: Vec2,
235}
236
237impl Pipeline {
238 pub fn init(config: &EngineConfig) -> Self {
240 let event_loop = EventLoop::new().expect("EventLoop::new");
242
243 let window_attrs = Window::default_attributes()
245 .with_title(&config.window_title)
246 .with_inner_size(LogicalSize::new(config.window_width, config.window_height))
247 .with_resizable(true);
248
249 let template = ConfigTemplateBuilder::new()
251 .with_alpha_size(8)
252 .with_depth_size(0);
253
254 let display_builder = DisplayBuilder::new()
255 .with_window_attributes(Some(window_attrs));
256
257 let (window, gl_config) = display_builder
258 .build(&event_loop, template, |mut configs| {
259 configs.next().expect("no suitable GL config found")
260 })
261 .expect("DisplayBuilder::build failed");
262
263 let window = window.expect("window was not created");
264 let display = gl_config.display();
265
266 let raw_handle = window.window_handle().unwrap().as_raw();
268 let ctx_attrs = ContextAttributesBuilder::new()
269 .with_context_api(ContextApi::OpenGl(Some(Version::new(3, 3))))
270 .build(Some(raw_handle));
271
272 let not_current = unsafe {
273 display.create_context(&gl_config, &ctx_attrs)
274 .expect("create_context failed")
275 };
276
277 let size = window.inner_size();
279 let w = size.width.max(1);
280 let h = size.height.max(1);
281
282 let surface_attrs = window
283 .build_surface_attributes(Default::default())
284 .expect("build_surface_attributes failed");
285
286 let surface = unsafe {
287 display.create_window_surface(&gl_config, &surface_attrs)
288 .expect("create_window_surface failed")
289 };
290
291 let context = not_current.make_current(&surface)
293 .expect("make_current failed");
294
295 let gl = unsafe {
297 glow::Context::from_loader_function(|sym| {
298 let sym_c = CString::new(sym).unwrap();
299 display.get_proc_address(sym_c.as_c_str()) as *const _
300 })
301 };
302
303 let program = unsafe { compile_program(&gl, VERT_SRC, FRAG_SRC) };
305 let loc_view_proj = unsafe {
306 gl.get_uniform_location(program, "u_view_proj")
307 .expect("uniform u_view_proj not found")
308 };
309 unsafe {
310 gl.use_program(Some(program));
311 if let Some(loc) = gl.get_uniform_location(program, "u_atlas") {
312 gl.uniform_1_i32(Some(&loc), 0);
313 }
314 }
315
316 let (vao, quad_vbo, instance_vbo) = unsafe { setup_vao(&gl) };
318
319 let atlas = FontAtlas::build(config.render.font_size as f32);
321 let atlas_tex = unsafe { upload_atlas(&gl, &atlas) };
322
323 let postfx = unsafe { PostFxPipeline::new(&gl, w, h) };
325
326 unsafe {
328 gl.enable(glow::BLEND);
329 gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
330 gl.clear_color(0.02, 0.02, 0.05, 1.0);
331 gl.viewport(0, 0, w as i32, h as i32);
332 }
333
334 log::info!(
335 "Pipeline ready — {}×{} — font atlas {}×{} ({} chars) — PostFxPipeline wired",
336 w, h, atlas.width, atlas.height, atlas.uvs.len()
337 );
338
339 Self {
340 width: w, height: h,
341 stats: FrameStats::default(),
342 running: true,
343 render_config: config.render.clone(),
344 event_loop, window, surface, context,
345 gl, program, vao, quad_vbo, instance_vbo, atlas_tex, loc_view_proj,
346 postfx,
347 atlas,
348 instances: Vec::with_capacity(8192),
349 fps_counter: FpsCounter::new(),
350 frame_start: Instant::now(),
351 scene_time: 0.0,
352 mouse_pos: Vec2::ZERO,
353 mouse_pos_prev: Vec2::ZERO,
354 mouse_ndc: Vec2::ZERO,
355 }
356 }
357
358 pub fn update_render_config(&mut self, config: &RenderConfig) {
361 self.render_config = config.clone();
362 }
363
364 pub fn poll_events(&mut self, input: &mut InputState) -> bool {
366 input.clear_frame();
367 self.mouse_pos_prev = self.mouse_pos;
368
369 let mut should_exit = false;
370 let mut resize: Option<(u32, u32)> = None;
371 let mut key_events: Vec<(KeyCode, bool)> = Vec::new();
372 let mut mouse_moved: Option<(f64, f64)> = None;
373 let mut mouse_buttons: Vec<(MouseButton, bool)> = Vec::new();
374 let mut scroll_delta: f32 = 0.0;
375
376 #[allow(deprecated)]
377 let status = self.event_loop.pump_events(Some(Duration::ZERO), |event, elwt| {
378 match event {
379 Event::WindowEvent { event: we, .. } => match we {
380 WindowEvent::CloseRequested => {
381 should_exit = true;
382 elwt.exit();
383 }
384 WindowEvent::Resized(s) => {
385 resize = Some((s.width, s.height));
386 }
387 WindowEvent::KeyboardInput { event: key_ev, .. } => {
388 if let PhysicalKey::Code(kc) = key_ev.physical_key {
389 let pressed = key_ev.state == ElementState::Pressed;
390 key_events.push((kc, pressed));
391 }
392 }
393 WindowEvent::CursorMoved { position, .. } => {
394 mouse_moved = Some((position.x, position.y));
395 }
396 WindowEvent::MouseInput { button, state, .. } => {
397 let pressed = state == ElementState::Pressed;
398 mouse_buttons.push((button, pressed));
399 }
400 WindowEvent::MouseWheel { delta, .. } => {
401 scroll_delta += match delta {
402 MouseScrollDelta::LineDelta(_, y) => y,
403 MouseScrollDelta::PixelDelta(d) => d.y as f32 / 40.0,
404 };
405 }
406 _ => {}
407 }
408 _ => {}
409 }
410 });
411
412 if let Some((w, h)) = resize {
414 if w > 0 && h > 0 {
415 self.surface.resize(
416 &self.context,
417 NonZeroU32::new(w).unwrap(),
418 NonZeroU32::new(h).unwrap(),
419 );
420 unsafe { self.gl.viewport(0, 0, w as i32, h as i32); }
421 self.width = w;
422 self.height = h;
423 input.window_resized = Some((w, h));
424 unsafe { self.postfx.resize(&self.gl, w, h); }
425 }
426 }
427
428 for (kc, pressed) in key_events {
430 if let Some(key) = keycode_to_engine(kc) {
431 if pressed {
432 input.keys_pressed.insert(key);
433 input.keys_just_pressed.insert(key);
434 } else {
435 input.keys_pressed.remove(&key);
436 input.keys_just_released.insert(key);
437 }
438 }
439 }
440
441 if let Some((x, y)) = mouse_moved {
443 self.mouse_pos = Vec2::new(x as f32, y as f32);
444 input.mouse_x = x as f32;
445 input.mouse_y = y as f32;
446 let w = self.width.max(1) as f32;
448 let h = self.height.max(1) as f32;
449 self.mouse_ndc = Vec2::new(
450 (x as f32 / w) * 2.0 - 1.0,
451 1.0 - (y as f32 / h) * 2.0,
452 );
453 input.mouse_ndc = self.mouse_ndc;
454 input.mouse_delta = self.mouse_pos - self.mouse_pos_prev;
455 }
456
457 for (button, pressed) in mouse_buttons {
458 match button {
459 MouseButton::Left => {
460 if pressed { input.mouse_left_just_pressed = true; }
461 else { input.mouse_left_just_released = true; }
462 input.mouse_left = pressed;
463 }
464 MouseButton::Right => {
465 if pressed { input.mouse_right_just_pressed = true; }
466 else { input.mouse_right_just_released = true; }
467 input.mouse_right = pressed;
468 }
469 MouseButton::Middle => {
470 if pressed { input.mouse_middle_just_pressed = true; }
471 input.mouse_middle = pressed;
472 }
473 _ => {}
474 }
475 }
476
477 input.scroll_delta = scroll_delta;
478
479 if should_exit || matches!(status, PumpStatus::Exit(_)) {
481 self.running = false;
482 }
483 self.running
484 }
485
486 pub fn render(&mut self, scene: &Scene, camera: &ProofCamera) {
489 let now = Instant::now();
491 let dt = now.duration_since(self.frame_start).as_secs_f32().min(0.1);
492 self.frame_start = now;
493 self.scene_time = scene.time;
494
495 self.fps_counter.push(dt);
496 self.stats.fps = self.fps_counter.fps();
497 self.stats.dt = dt;
498 self.stats.frame_number += 1;
499
500 let pos = camera.position.position();
502 let tgt = camera.target.position();
503 let fov = camera.fov.position;
504 let aspect = if self.height > 0 { self.width as f32 / self.height as f32 } else { 1.0 };
505 let view = Mat4::look_at_rh(pos, tgt, Vec3::Y);
506 let proj = Mat4::perspective_rh_gl(fov.to_radians(), aspect, camera.near, camera.far);
507 let view_proj = proj * view;
508
509 self.instances.clear();
511 let mut glyph_count = 0;
512 let mut particle_count = 0;
513
514 for (_, glyph) in scene.glyphs.iter() {
516 if !glyph.visible { continue; }
517 let life_scale = if let Some(ref f) = glyph.life_function {
518 f.evaluate(scene.time, 0.0)
519 } else {
520 1.0
521 };
522 let uv = self.atlas.uv_for(glyph.character);
523 self.instances.push(GlyphInstance {
524 position: glyph.position.to_array(),
525 scale: [glyph.scale.x * life_scale, glyph.scale.y * life_scale],
526 rotation: glyph.rotation,
527 color: glyph.color.to_array(),
528 emission: glyph.emission,
529 glow_color: glyph.glow_color.to_array(),
530 glow_radius: glyph.glow_radius,
531 uv_offset: uv.offset(),
532 uv_size: uv.size(),
533 _pad: [0.0; 2],
534 });
535 glyph_count += 1;
536 }
537
538 for particle in scene.particles.iter() {
539 let g = &particle.glyph;
540 if !g.visible { continue; }
541 let uv = self.atlas.uv_for(g.character);
542 self.instances.push(GlyphInstance {
543 position: g.position.to_array(),
544 scale: [g.scale.x, g.scale.y],
545 rotation: g.rotation,
546 color: g.color.to_array(),
547 emission: g.emission,
548 glow_color: g.glow_color.to_array(),
549 glow_radius: g.glow_radius,
550 uv_offset: uv.offset(),
551 uv_size: uv.size(),
552 _pad: [0.0; 2],
553 });
554 particle_count += 1;
555 }
556
557 self.stats.glyph_count = glyph_count;
558 self.stats.particle_count = particle_count;
559 self.stats.draw_calls = 0;
560
561 unsafe { self.execute_render_passes(view_proj); }
563 }
564
565 pub fn swap(&mut self) -> bool {
567 if let Err(e) = self.surface.swap_buffers(&self.context) {
568 log::error!("swap_buffers failed: {e}");
569 self.running = false;
570 }
571 self.running
572 }
573
574 unsafe fn execute_render_passes(&mut self, view_proj: Mat4) {
577 let gl = &self.gl;
578
579 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.postfx.scene_fbo));
584 gl.viewport(0, 0, self.width as i32, self.height as i32);
585 gl.clear(glow::COLOR_BUFFER_BIT);
586
587 if !self.instances.is_empty() {
588 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instance_vbo));
590 gl.buffer_data_u8_slice(
591 glow::ARRAY_BUFFER,
592 cast_slice(self.instances.as_slice()),
593 glow::DYNAMIC_DRAW,
594 );
595
596 gl.use_program(Some(self.program));
598 gl.uniform_matrix_4_f32_slice(
599 Some(&self.loc_view_proj),
600 false,
601 &view_proj.to_cols_array(),
602 );
603 gl.active_texture(glow::TEXTURE0);
604 gl.bind_texture(glow::TEXTURE_2D, Some(self.atlas_tex));
605 gl.bind_vertex_array(Some(self.vao));
606 gl.draw_arrays_instanced(glow::TRIANGLES, 0, 6, self.instances.len() as i32);
607 self.stats.draw_calls += 1;
608 }
609
610 self.postfx.run(gl, &self.render_config, self.width, self.height, self.scene_time);
615 self.stats.draw_calls += 4; }
617}
618
619unsafe fn compile_program(gl: &glow::Context, vert_src: &str, frag_src: &str) -> glow::Program {
623 let vs = gl.create_shader(glow::VERTEX_SHADER).expect("create vertex shader");
624 gl.shader_source(vs, vert_src);
625 gl.compile_shader(vs);
626 if !gl.get_shader_compile_status(vs) {
627 let log = gl.get_shader_info_log(vs);
628 panic!("Vertex shader compile error:\n{log}");
629 }
630
631 let fs = gl.create_shader(glow::FRAGMENT_SHADER).expect("create fragment shader");
632 gl.shader_source(fs, frag_src);
633 gl.compile_shader(fs);
634 if !gl.get_shader_compile_status(fs) {
635 let log = gl.get_shader_info_log(fs);
636 panic!("Fragment shader compile error:\n{log}");
637 }
638
639 let prog = gl.create_program().expect("create shader program");
640 gl.attach_shader(prog, vs);
641 gl.attach_shader(prog, fs);
642 gl.link_program(prog);
643 if !gl.get_program_link_status(prog) {
644 let log = gl.get_program_info_log(prog);
645 panic!("Shader link error:\n{log}");
646 }
647
648 gl.detach_shader(prog, vs);
649 gl.detach_shader(prog, fs);
650 gl.delete_shader(vs);
651 gl.delete_shader(fs);
652 prog
653}
654
655unsafe fn setup_vao(gl: &glow::Context) -> (glow::VertexArray, glow::Buffer, glow::Buffer) {
657 let vao = gl.create_vertex_array().expect("create vao");
658 gl.bind_vertex_array(Some(vao));
659
660 let quad_vbo = gl.create_buffer().expect("create quad_vbo");
662 gl.bind_buffer(glow::ARRAY_BUFFER, Some(quad_vbo));
663 gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, cast_slice(&QUAD_VERTS), glow::STATIC_DRAW);
664 gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 16, 0);
666 gl.enable_vertex_attrib_array(0);
667 gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, 16, 8);
669 gl.enable_vertex_attrib_array(1);
670
671 let instance_vbo = gl.create_buffer().expect("create instance_vbo");
673 gl.bind_buffer(glow::ARRAY_BUFFER, Some(instance_vbo));
674
675 let stride = std::mem::size_of::<GlyphInstance>() as i32;
676
677 macro_rules! inst_attr {
679 ($loc:expr, $count:expr, $off:expr) => {{
680 gl.vertex_attrib_pointer_f32($loc, $count, glow::FLOAT, false, stride, $off);
681 gl.enable_vertex_attrib_array($loc);
682 gl.vertex_attrib_divisor($loc, 1); }};
684 }
685
686 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)
698}
699
700unsafe fn upload_atlas(gl: &glow::Context, atlas: &FontAtlas) -> glow::Texture {
702 let tex = gl.create_texture().expect("create atlas texture");
703 gl.bind_texture(glow::TEXTURE_2D, Some(tex));
704 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
705 gl.tex_image_2d(
706 glow::TEXTURE_2D, 0, glow::R8 as i32,
707 atlas.width as i32, atlas.height as i32,
708 0, glow::RED, glow::UNSIGNED_BYTE,
709 Some(&atlas.pixels),
710 );
711 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32);
712 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32);
713 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
714 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
715 tex
716}
717
718fn keycode_to_engine(kc: KeyCode) -> Option<Key> {
722 Some(match kc {
723 KeyCode::KeyA => Key::A, KeyCode::KeyB => Key::B, KeyCode::KeyC => Key::C,
724 KeyCode::KeyD => Key::D, KeyCode::KeyE => Key::E, KeyCode::KeyF => Key::F,
725 KeyCode::KeyG => Key::G, KeyCode::KeyH => Key::H, KeyCode::KeyI => Key::I,
726 KeyCode::KeyJ => Key::J, KeyCode::KeyK => Key::K, KeyCode::KeyL => Key::L,
727 KeyCode::KeyM => Key::M, KeyCode::KeyN => Key::N, KeyCode::KeyO => Key::O,
728 KeyCode::KeyP => Key::P, KeyCode::KeyQ => Key::Q, KeyCode::KeyR => Key::R,
729 KeyCode::KeyS => Key::S, KeyCode::KeyT => Key::T, KeyCode::KeyU => Key::U,
730 KeyCode::KeyV => Key::V, KeyCode::KeyW => Key::W, KeyCode::KeyX => Key::X,
731 KeyCode::KeyY => Key::Y, KeyCode::KeyZ => Key::Z,
732 KeyCode::Digit1 => Key::Num1, KeyCode::Digit2 => Key::Num2,
733 KeyCode::Digit3 => Key::Num3, KeyCode::Digit4 => Key::Num4,
734 KeyCode::Digit5 => Key::Num5, KeyCode::Digit6 => Key::Num6,
735 KeyCode::Digit7 => Key::Num7, KeyCode::Digit8 => Key::Num8,
736 KeyCode::Digit9 => Key::Num9, KeyCode::Digit0 => Key::Num0,
737 KeyCode::ArrowUp => Key::Up, KeyCode::ArrowDown => Key::Down,
738 KeyCode::ArrowLeft => Key::Left, KeyCode::ArrowRight => Key::Right,
739 KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
740 KeyCode::Escape => Key::Escape,
741 KeyCode::Space => Key::Space,
742 KeyCode::Backspace => Key::Backspace,
743 KeyCode::Tab => Key::Tab,
744 KeyCode::ShiftLeft => Key::LShift, KeyCode::ShiftRight => Key::RShift,
745 KeyCode::ControlLeft => Key::LCtrl, KeyCode::ControlRight => Key::RCtrl,
746 KeyCode::AltLeft => Key::LAlt, KeyCode::AltRight => Key::RAlt,
747 KeyCode::F1 => Key::F1, KeyCode::F2 => Key::F2, KeyCode::F3 => Key::F3,
748 KeyCode::F4 => Key::F4, KeyCode::F5 => Key::F5, KeyCode::F6 => Key::F6,
749 KeyCode::F7 => Key::F7, KeyCode::F8 => Key::F8, KeyCode::F9 => Key::F9,
750 KeyCode::F10 => Key::F10, KeyCode::F11 => Key::F11, KeyCode::F12 => Key::F12,
751 KeyCode::Slash => Key::Slash,
752 KeyCode::Backslash => Key::Backslash,
753 KeyCode::Period => Key::Period,
754 KeyCode::Comma => Key::Comma,
755 KeyCode::Semicolon => Key::Semicolon,
756 KeyCode::Quote => Key::Quote,
757 KeyCode::BracketLeft => Key::LBracket,
758 KeyCode::BracketRight => Key::RBracket,
759 KeyCode::Minus => Key::Minus,
760 KeyCode::Equal => Key::Equals,
761 KeyCode::Backquote => Key::Backtick,
762 KeyCode::PageUp => Key::PageUp,
763 KeyCode::PageDown => Key::PageDown,
764 KeyCode::Home => Key::Home,
765 KeyCode::End => Key::End,
766 KeyCode::Insert => Key::Insert,
767 KeyCode::Delete => Key::Delete,
768 _ => return None,
769 })
770}