1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum VmKey {
13 Escape,
14 Enter,
15 Space,
16 Backspace,
17 Delete,
18 Tab,
19 Shift,
20 Control,
21 Meta,
22 Alt,
23 Home,
24 End,
25 ArrowUp,
26 ArrowDown,
27 ArrowLeft,
28 ArrowRight,
29 F(u8),
31 Digit(u8),
33 Letter(char),
35 Other(u32),
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum VmMouseButton {
41 Left,
42 Right,
43 Middle,
44 Other(u8),
45}
46
47#[derive(Debug, Clone, Copy)]
48struct KeyState {
49 down: bool,
50 down_stock: bool,
51 up_stock: bool,
52 down_up_stock: u8,
56 flick_stock: bool,
57 flick_angle: f32,
58 flick_pixel: f32,
59 flick_mm: f32,
60 flick_start: Option<(i32, i32)>,
61}
62
63impl KeyState {
64 const fn new() -> Self {
65 Self {
66 down: false,
67 down_stock: false,
68 up_stock: false,
69 down_up_stock: 0,
70 flick_stock: false,
71 flick_angle: 0.0,
72 flick_pixel: 0.0,
73 flick_mm: 0.0,
74 flick_start: None,
75 }
76 }
77
78 fn clear_all(&mut self) {
79 self.down = false;
80 self.down_stock = false;
81 self.up_stock = false;
82 self.down_up_stock = 0;
83 self.flick_stock = false;
84 self.flick_angle = 0.0;
85 self.flick_pixel = 0.0;
86 self.flick_mm = 0.0;
87 self.flick_start = None;
88 }
89
90 fn clear_stocks(&mut self) {
91 self.down_stock = false;
92 self.up_stock = false;
93 if self.down_up_stock == 2 {
96 self.down_up_stock = 0;
97 }
98 self.flick_stock = false;
99 }
100
101 fn use_stocks(&mut self) {
102 self.down_stock = false;
103 self.up_stock = false;
104 self.down_up_stock = 0;
105 self.flick_stock = false;
106 }
107}
108
109pub const JOYPAD_KEY_COUNT: usize = 32;
114
115#[derive(Debug, Clone)]
116pub struct InputState {
117 keys: [KeyState; 256],
118 joypad_keys: [KeyState; JOYPAD_KEY_COUNT],
123
124 pub mouse_x: i32,
125 pub mouse_y: i32,
126 mouse_position_valid: bool,
127
128 wheel_delta: i32,
129
130 pub last_key_down: Option<VmKey>,
132 pub last_mouse_down: Option<VmMouseButton>,
134
135 joypad_mode_active: bool,
139}
140
141impl Default for InputState {
142 fn default() -> Self {
143 Self {
144 keys: [KeyState::new(); 256],
145 joypad_keys: [KeyState::new(); JOYPAD_KEY_COUNT],
146 mouse_x: -1,
147 mouse_y: -1,
148 mouse_position_valid: false,
149 wheel_delta: 0,
150 last_key_down: None,
151 last_mouse_down: None,
152 joypad_mode_active: false,
153 }
154 }
155}
156
157impl InputState {
158 pub fn joypad_mode_active(&self) -> bool {
160 self.joypad_mode_active
161 }
162
163 pub fn note_joypad_activity(&mut self) {
166 self.joypad_mode_active = true;
167 }
168
169 pub fn joypad_is_down(&self, key_no: usize) -> bool {
171 self.joypad_keys.get(key_no).is_some_and(|st| st.down)
172 }
173
174 pub fn joypad_down_stock(&self, key_no: usize) -> bool {
176 self.joypad_keys.get(key_no).is_some_and(|st| st.down_stock)
177 }
178
179 pub fn joypad_up_stock(&self, key_no: usize) -> bool {
181 self.joypad_keys.get(key_no).is_some_and(|st| st.up_stock)
182 }
183
184 pub fn joypad_down_up_stock(&self, key_no: usize) -> bool {
186 self.joypad_keys
187 .get(key_no)
188 .is_some_and(|st| st.down_up_stock == 2)
189 }
190
191 pub fn on_joypad_key_down(&mut self, key_no: usize) {
195 let Some(st) = self.joypad_keys.get_mut(key_no) else {
196 return;
197 };
198 self.joypad_mode_active = true;
199 if !st.down {
200 st.down = true;
201 st.down_stock = true;
202 }
203 if st.down_up_stock == 0 {
204 st.down_up_stock = 1;
205 }
206 if st.down_stock && st.up_stock {
207 st.down_up_stock = 2;
208 }
209 }
210
211 pub fn on_joypad_key_up(&mut self, key_no: usize) {
213 if key_no >= JOYPAD_KEY_COUNT {
214 return;
215 }
216 self.joypad_mode_active = true;
217 let st = &mut self.joypad_keys[key_no];
218 if st.down {
219 st.down = false;
220 st.up_stock = true;
221 if st.down_up_stock == 1 {
222 st.down_up_stock = 2;
223 }
224 }
225 }
226
227 fn note_keyboard_mouse_activity(&mut self) {
228 self.joypad_mode_active = false;
229 }
230
231 pub fn resync_after_native_ui(&mut self) {
234 self.use_current();
235 }
236
237 pub fn vk_is_down(&self, vk: u8) -> bool {
243 self.keys[vk as usize].down
244 }
245
246 pub fn vk_down_stock(&self, vk: u8) -> bool {
248 self.keys[vk as usize].down_stock
249 }
250
251 pub fn vk_up_stock(&self, vk: u8) -> bool {
253 self.keys[vk as usize].up_stock
254 }
255
256 pub fn vk_down_up_stock(&self, vk: u8) -> bool {
258 self.keys[vk as usize].down_up_stock == 2
259 }
260
261 pub fn use_vk_down_stock(&mut self, vk: u8) -> bool {
267 let st = &mut self.keys[vk as usize];
268 if !st.down_stock {
269 return false;
270 }
271 st.down_stock = false;
272 st.down_up_stock = 0;
273 true
274 }
275
276 pub fn use_vk_up_stock(&mut self, vk: u8) -> bool {
278 let st = &mut self.keys[vk as usize];
279 if !st.up_stock {
280 return false;
281 }
282 st.up_stock = false;
283 st.down_up_stock = 0;
284 true
285 }
286
287 pub fn use_vk_down_up_stock(&mut self, vk: u8) -> bool {
290 let st = &mut self.keys[vk as usize];
291 if st.down_up_stock != 2 {
292 return false;
293 }
294 st.down_stock = false;
295 st.up_stock = false;
296 st.down_up_stock = 0;
297 true
298 }
299
300 pub fn vk_flick_stock(&self, vk: u8) -> bool {
302 self.keys[vk as usize].flick_stock
303 }
304
305 pub fn vk_flick_angle(&self, vk: u8) -> f32 {
307 self.keys[vk as usize].flick_angle
308 }
309
310 pub fn vk_flick_pixel(&self, vk: u8) -> f32 {
312 self.keys[vk as usize].flick_pixel
313 }
314
315 pub fn vk_flick_mm(&self, vk: u8) -> f32 {
317 self.keys[vk as usize].flick_mm
318 }
319
320 fn vk_set_down(&mut self, vk: u8) {
321 let st = &mut self.keys[vk as usize];
322 if !st.down {
323 st.down = true;
324 st.down_stock = true;
325 }
326 if st.down_up_stock == 0 {
327 st.down_up_stock = 1;
328 }
329 if st.down_stock && st.up_stock {
331 st.down_up_stock = 2;
332 }
333 }
334
335 fn vk_set_up(&mut self, vk: u8) {
336 let st = &mut self.keys[vk as usize];
337 if st.down {
338 st.down = false;
339 st.up_stock = true;
340 if st.down_up_stock == 1 {
341 st.down_up_stock = 2;
342 }
343 }
344 }
345
346 fn vk_set_flick_start(&mut self, vk: u8) {
347 if !is_mouse_vk(vk) {
348 return;
349 }
350 let st = &mut self.keys[vk as usize];
351 st.flick_stock = false;
352 st.flick_start = Some((self.mouse_x, self.mouse_y));
353 }
354
355 fn vk_set_flick_end(&mut self, vk: u8) {
356 if !is_mouse_vk(vk) {
357 return;
358 }
359 let st = &mut self.keys[vk as usize];
360 let Some((sx, sy)) = st.flick_start.take() else {
361 return;
362 };
363 let dx = (self.mouse_x - sx) as f32;
364 let dy = (self.mouse_y - sy) as f32;
365 let dist = (dx * dx + dy * dy).sqrt();
366 if dist < FLICK_MIN_PIXEL {
367 return;
368 }
369 st.flick_angle = dx.atan2(dy);
371 st.flick_pixel = dist;
372 st.flick_mm = dist * MM_PER_PX;
373 st.flick_stock = true;
374 }
375
376 pub fn clear_all(&mut self) {
378 for st in &mut self.keys {
379 st.clear_all();
380 }
381 for st in &mut self.joypad_keys {
382 st.clear_all();
383 }
384 self.wheel_delta = 0;
385 self.last_key_down = None;
386 self.last_mouse_down = None;
387 self.joypad_mode_active = false;
388 }
389
390 pub fn has_mouse_position(&self) -> bool {
391 self.mouse_position_valid
392 }
393
394 pub fn clear_keyboard(&mut self) {
396 for (idx, st) in self.keys.iter_mut().enumerate() {
397 if matches!(idx as u8, 0x01 | 0x02 | 0x04) {
398 continue;
399 }
400 st.clear_all();
401 }
402 self.last_key_down = None;
403 }
404
405 pub fn clear_mouse(&mut self) {
407 for vk in [0x01usize, 0x02usize, 0x04usize] {
408 self.keys[vk].clear_all();
409 }
410 self.wheel_delta = 0;
411 self.last_mouse_down = None;
412 }
413
414 pub fn use_current(&mut self) {
419 for st in &mut self.keys {
420 st.use_stocks();
421 }
422 for st in &mut self.joypad_keys {
423 st.use_stocks();
424 }
425 self.wheel_delta = 0;
426 self.last_key_down = None;
427 self.last_mouse_down = None;
428 }
429
430 pub fn next_frame(&mut self) {
432 for st in &mut self.keys {
433 st.clear_stocks();
434 }
435 for st in &mut self.joypad_keys {
436 st.clear_stocks();
437 }
438 self.wheel_delta = 0;
439 }
440
441 pub fn next_keyboard_frame(&mut self) {
443 for (idx, st) in self.keys.iter_mut().enumerate() {
444 if matches!(idx as u8, 0x01 | 0x02 | 0x04) {
445 continue;
446 }
447 st.clear_stocks();
448 }
449 self.last_key_down = None;
450 }
451
452 pub fn next_mouse_frame(&mut self) {
454 for vk in [0x01usize, 0x02usize, 0x04usize] {
455 self.keys[vk].clear_stocks();
456 }
457 self.wheel_delta = 0;
458 self.last_mouse_down = None;
459 }
460
461 pub fn on_mouse_wheel(&mut self, delta_y: i32) {
466 self.note_keyboard_mouse_activity();
467 self.wheel_delta = self.wheel_delta.saturating_add(delta_y);
468 }
469
470 pub fn take_wheel_delta(&mut self) -> i32 {
472 let v = self.wheel_delta;
473 self.wheel_delta = 0;
474 v
475 }
476
477 pub fn is_key_down(&self, k: VmKey) -> bool {
482 vmkey_to_vk(k)
483 .map(|vk| self.vk_is_down(vk))
484 .unwrap_or(false)
485 }
486
487 pub fn is_mouse_down(&self, b: VmMouseButton) -> bool {
488 match b {
489 VmMouseButton::Left => self.vk_is_down(0x01),
490 VmMouseButton::Right => self.vk_is_down(0x02),
491 VmMouseButton::Middle => self.vk_is_down(0x04),
492 VmMouseButton::Other(_) => false,
493 }
494 }
495
496 pub fn on_key_down(&mut self, k: VmKey) {
497 self.note_keyboard_mouse_activity();
498 if let Some(vk) = vmkey_to_vk(k) {
499 self.vk_set_down(vk);
500 }
501 self.last_key_down = Some(k);
502 }
503
504 pub fn on_key_up(&mut self, k: VmKey) {
505 if let Some(vk) = vmkey_to_vk(k) {
506 self.vk_set_up(vk);
507 }
508 }
509
510 pub fn on_mouse_down(&mut self, b: VmMouseButton) {
511 self.note_keyboard_mouse_activity();
512 match b {
513 VmMouseButton::Left => {
514 self.vk_set_down(0x01);
515 self.vk_set_flick_start(0x01);
516 }
517 VmMouseButton::Right => {
518 self.vk_set_down(0x02);
519 self.vk_set_flick_start(0x02);
520 }
521 VmMouseButton::Middle => {
522 self.vk_set_down(0x04);
523 self.vk_set_flick_start(0x04);
524 }
525 VmMouseButton::Other(_) => {}
526 }
527 self.last_mouse_down = Some(b);
528 }
529
530 pub fn on_mouse_up(&mut self, b: VmMouseButton) {
531 match b {
532 VmMouseButton::Left => {
533 self.vk_set_flick_end(0x01);
534 self.vk_set_up(0x01);
535 }
536 VmMouseButton::Right => {
537 self.vk_set_flick_end(0x02);
538 self.vk_set_up(0x02);
539 }
540 VmMouseButton::Middle => {
541 self.vk_set_flick_end(0x04);
542 self.vk_set_up(0x04);
543 }
544 VmMouseButton::Other(_) => {}
545 }
546 }
547
548 pub fn on_mouse_move(&mut self, x: i32, y: i32) {
549 self.note_keyboard_mouse_activity();
550 self.mouse_x = x;
551 self.mouse_y = y;
552 self.mouse_position_valid = true;
553 }
554
555 pub fn dir_mask(&self) -> i64 {
560 let mut m = 0;
561 if self.vk_is_down(0x25) {
562 m |= 1;
563 }
564 if self.vk_is_down(0x27) {
565 m |= 2;
566 }
567 if self.vk_is_down(0x26) {
568 m |= 4;
569 }
570 if self.vk_is_down(0x28) {
571 m |= 8;
572 }
573 m
574 }
575}
576
577pub(crate) fn vmkey_to_vk_code(k: VmKey) -> Option<u8> {
578 vmkey_to_vk(k)
579}
580
581fn vmkey_to_vk(k: VmKey) -> Option<u8> {
582 match k {
583 VmKey::Escape => Some(0x1B),
584 VmKey::Enter => Some(0x0D),
585 VmKey::Space => Some(0x20),
586 VmKey::Backspace => Some(0x08),
587 VmKey::Delete => Some(0x2E),
588 VmKey::Tab => Some(0x09),
589 VmKey::Shift => Some(0x10),
590 VmKey::Control => Some(0x11),
591 VmKey::Meta => Some(0x5B),
592 VmKey::Alt => Some(0x12),
593 VmKey::Home => Some(0x24),
594 VmKey::End => Some(0x23),
595
596 VmKey::ArrowLeft => Some(0x25),
597 VmKey::ArrowUp => Some(0x26),
598 VmKey::ArrowRight => Some(0x27),
599 VmKey::ArrowDown => Some(0x28),
600
601 VmKey::F(n @ 1..=12) => Some(0x6F + n), VmKey::Digit(n @ 0..=9) => Some(0x30 + n),
603 VmKey::Letter(c) => {
604 let uc = c.to_ascii_uppercase();
605 if uc.is_ascii_uppercase() {
606 Some(uc as u8)
607 } else {
608 None
609 }
610 }
611 VmKey::Other(_) => None,
612 _ => None,
613 }
614}
615
616fn is_mouse_vk(vk: u8) -> bool {
617 matches!(vk, 0x01 | 0x02 | 0x04)
618}
619
620const FLICK_MIN_PIXEL: f32 = 30.0;
621const MM_PER_PX: f32 = 25.4 / 96.0;
622
623#[cfg(test)]
624mod joypad_mode_tests {
625 use super::{InputState, VmKey};
626
627 #[test]
628 fn keyboard_activity_leaves_joypad_mode() {
629 let mut input = InputState::default();
630 input.note_joypad_activity();
631 assert!(input.joypad_mode_active());
632 input.on_key_down(VmKey::Enter);
633 assert!(!input.joypad_mode_active());
634 }
635
636 #[test]
637 fn native_ui_resync_consumes_edges_but_keeps_mode() {
638 let mut input = InputState::default();
639 input.note_joypad_activity();
640 input.on_key_down(VmKey::Enter);
641 input.note_joypad_activity();
642 input.resync_after_native_ui();
643 assert!(input.joypad_mode_active());
644 assert!(!input.vk_down_stock(0x0d));
645 assert!(input.vk_is_down(0x0d));
646 }
647
648 #[test]
649 fn joypad_key_uses_button_stock_lifecycle() {
650 let mut input = InputState::default();
651 input.on_joypad_key_down(0);
652 assert!(input.joypad_mode_active());
653 assert!(input.joypad_is_down(0));
654 assert!(input.joypad_down_stock(0));
655 assert!(!input.joypad_down_up_stock(0));
656
657 input.next_frame();
658 assert!(input.joypad_is_down(0));
659 assert!(!input.joypad_down_stock(0));
660
661 input.on_joypad_key_up(0);
662 assert!(!input.joypad_is_down(0));
663 assert!(input.joypad_up_stock(0));
664 assert!(input.joypad_down_up_stock(0));
665 }
666
667 #[test]
668 fn consuming_down_prevents_release_from_becoming_down_up() {
669 let mut input = InputState::default();
670 input.on_mouse_down(super::VmMouseButton::Left);
671 assert!(input.vk_down_stock(0x01));
672 assert!(input.use_vk_down_stock(0x01));
673 assert!(!input.vk_down_stock(0x01));
674
675 input.on_mouse_up(super::VmMouseButton::Left);
676 assert!(input.vk_up_stock(0x01));
677 assert!(!input.vk_down_up_stock(0x01));
678 }
679
680 #[test]
681 fn consuming_up_or_down_up_matches_tona_button_stock_semantics() {
682 let mut input = InputState::default();
683 input.on_key_down(VmKey::Enter);
684 input.on_key_up(VmKey::Enter);
685 assert!(input.vk_down_up_stock(0x0d));
686 assert!(input.use_vk_up_stock(0x0d));
687 assert!(!input.vk_up_stock(0x0d));
688 assert!(!input.vk_down_up_stock(0x0d));
689
690 input.on_key_down(VmKey::Enter);
691 input.on_key_up(VmKey::Enter);
692 assert!(input.use_vk_down_up_stock(0x0d));
693 assert!(!input.vk_down_stock(0x0d));
694 assert!(!input.vk_up_stock(0x0d));
695 assert!(!input.vk_down_up_stock(0x0d));
696 }
697
698 #[test]
699 fn joypad_keeps_complete_32_button_backend_domain() {
700 let mut input = InputState::default();
701
702 input.on_joypad_key_down(31);
705 assert!(input.joypad_is_down(31));
706 assert!(input.joypad_down_stock(31));
707
708 input.on_joypad_key_up(31);
709 assert!(!input.joypad_is_down(31));
710 assert!(input.joypad_up_stock(31));
711
712 input.on_joypad_key_down(32);
715 assert!(!input.joypad_is_down(32));
716 }
717}