use crate::components::InputKey;
use crate::gfx::keymap::KeyMap;
use windows::Win32::UI::Input::KeyboardAndMouse::*;
pub(crate) use crate::gfx::input::RenderInput as InputState;
#[derive(Clone, Copy)]
pub(crate) struct MouseSnapshot {
pub dx: f32,
pub dy: f32,
pub x: f32,
pub y: f32,
pub left_click: bool,
pub left_button_down: bool,
pub right_click: bool,
pub scroll_delta: f32,
}
#[derive(Default)]
pub(crate) struct KeyState {
pub forward: bool,
pub backward: bool,
pub left: bool,
pub right: bool,
pub sprint: bool,
pub ctrl: bool,
pub alt: bool,
pub interact_pending: bool,
pub jump_pending: bool,
pub hud_toggle_pending: bool,
pub escape_pending: bool,
pub captured_key: Option<InputKey>,
pub typed_char: Option<char>,
pub keymap: KeyMap,
}
impl KeyState {
pub(crate) fn set_keymap(&mut self, keymap: &KeyMap) {
self.keymap = *keymap;
}
fn apply_binding(&mut self, key: InputKey, down: bool, fire_pulse: bool) {
let km = self.keymap;
if km.forward == key {
self.forward = down;
}
if km.backward == key {
self.backward = down;
}
if km.left == key {
self.left = down;
}
if km.right == key {
self.right = down;
}
if km.sprint == key {
self.sprint = down;
}
if fire_pulse {
if km.jump == key {
self.jump_pending = true;
}
if km.interact == key {
self.interact_pending = true;
}
}
}
pub(crate) fn on_key_down(&mut self, vk: VIRTUAL_KEY) {
if vk == VK_F1 {
self.hud_toggle_pending = true;
}
if vk == VK_CONTROL {
self.ctrl = true;
}
if let Some(key) = key_from_vk(vk) {
self.captured_key = Some(key);
self.apply_binding(key, true, true);
}
}
pub(crate) fn on_escape_uncaptured(&mut self) {
self.escape_pending = true;
}
pub(crate) fn on_sys_key(&mut self, vk: VIRTUAL_KEY, down: bool) {
if vk == VK_MENU {
self.alt = down;
}
}
pub(crate) fn on_focus_lost(&mut self) {
self.ctrl = false;
self.alt = false;
}
pub(crate) fn on_key_up(&mut self, vk: VIRTUAL_KEY) {
if vk == VK_CONTROL {
self.ctrl = false;
}
if let Some(key) = key_from_vk(vk) {
self.apply_binding(key, false, false);
}
}
pub(crate) fn on_char(&mut self, c: char) {
if is_printable_glyph(c) {
self.typed_char = Some(c);
}
}
pub(crate) fn take(&mut self, mouse: MouseSnapshot) -> InputState {
let MouseSnapshot {
dx: mouse_dx,
dy: mouse_dy,
x: mouse_x,
y: mouse_y,
left_click,
left_button_down,
right_click,
scroll_delta,
} = mouse;
let s = InputState {
forward: self.forward,
backward: self.backward,
left: self.left,
right: self.right,
sprint: self.sprint,
interact: self.interact_pending,
jump: self.jump_pending,
mouse_dx,
mouse_dy,
scroll_delta,
mouse_x,
mouse_y,
left_click,
left_button_down,
right_click,
hud_toggle: self.hud_toggle_pending,
escape: self.escape_pending,
ctrl: self.ctrl,
alt: self.alt,
cmd: false,
captured_key: self.captured_key,
typed_char: self.typed_char,
};
self.interact_pending = false;
self.jump_pending = false;
self.hud_toggle_pending = false;
self.escape_pending = false;
self.captured_key = None;
self.typed_char = None;
s
}
}
pub(crate) fn vk_from_wparam(wparam: usize) -> VIRTUAL_KEY {
VIRTUAL_KEY(wparam as u16)
}
fn is_printable_glyph(c: char) -> bool {
!c.is_control()
}
fn key_from_vk(vk: VIRTUAL_KEY) -> Option<InputKey> {
Some(match vk {
VK_A => InputKey::A,
VK_B => InputKey::B,
VK_C => InputKey::C,
VK_D => InputKey::D,
VK_E => InputKey::E,
VK_F => InputKey::F,
VK_G => InputKey::G,
VK_H => InputKey::H,
VK_I => InputKey::I,
VK_J => InputKey::J,
VK_K => InputKey::K,
VK_L => InputKey::L,
VK_M => InputKey::M,
VK_N => InputKey::N,
VK_O => InputKey::O,
VK_P => InputKey::P,
VK_Q => InputKey::Q,
VK_R => InputKey::R,
VK_S => InputKey::S,
VK_T => InputKey::T,
VK_U => InputKey::U,
VK_V => InputKey::V,
VK_W => InputKey::W,
VK_X => InputKey::X,
VK_Y => InputKey::Y,
VK_Z => InputKey::Z,
VK_0 => InputKey::Num0,
VK_1 => InputKey::Num1,
VK_2 => InputKey::Num2,
VK_3 => InputKey::Num3,
VK_4 => InputKey::Num4,
VK_5 => InputKey::Num5,
VK_6 => InputKey::Num6,
VK_7 => InputKey::Num7,
VK_8 => InputKey::Num8,
VK_9 => InputKey::Num9,
VK_SPACE => InputKey::Space,
VK_TAB => InputKey::Tab,
VK_RETURN => InputKey::Enter,
VK_BACK => InputKey::Backspace,
VK_DELETE => InputKey::Delete,
VK_SHIFT => InputKey::Shift,
VK_LEFT => InputKey::Left,
VK_RIGHT => InputKey::Right,
VK_UP => InputKey::Up,
VK_DOWN => InputKey::Down,
VK_OEM_MINUS => InputKey::Minus,
VK_OEM_PLUS => InputKey::Equals,
VK_OEM_4 => InputKey::LeftBracket,
VK_OEM_6 => InputKey::RightBracket,
VK_OEM_5 => InputKey::Backslash,
VK_OEM_1 => InputKey::Semicolon,
VK_OEM_7 => InputKey::Quote,
VK_OEM_COMMA => InputKey::Comma,
VK_OEM_PERIOD => InputKey::Period,
VK_OEM_2 => InputKey::Slash,
VK_OEM_3 => InputKey::Backtick,
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn snapshot(ks: &mut KeyState) -> InputState {
ks.take(MouseSnapshot {
dx: 0.0,
dy: 0.0,
x: 0.0,
y: 0.0,
left_click: false,
left_button_down: false,
right_click: false,
scroll_delta: 0.0,
})
}
#[test]
fn control_key_tracks_held_ctrl_modifier() {
let mut ks = KeyState::default();
assert!(!snapshot(&mut ks).ctrl, "ctrl starts released");
ks.on_key_down(VK_CONTROL);
assert!(snapshot(&mut ks).ctrl, "ctrl held after VK_CONTROL down");
assert!(snapshot(&mut ks).ctrl, "ctrl stays held across frames");
ks.on_key_up(VK_CONTROL);
assert!(!snapshot(&mut ks).ctrl, "ctrl released after VK_CONTROL up");
}
#[test]
fn typed_char_carries_a_printable_glyph_once() {
let mut ks = KeyState::default();
assert_eq!(snapshot(&mut ks).typed_char, None, "starts empty");
ks.on_char('A');
assert_eq!(
snapshot(&mut ks).typed_char,
Some('A'),
"printable glyph surfaces in the snapshot"
);
assert_eq!(snapshot(&mut ks).typed_char, None, "cleared after take");
}
#[test]
fn control_chars_do_not_become_typed_chars() {
let mut ks = KeyState::default();
for c in ['\u{08}', '\r', '\n', '\u{1b}', '\t', '\u{7f}'] {
ks.on_char(c);
}
assert_eq!(
snapshot(&mut ks).typed_char,
None,
"control chars are not printable glyphs"
);
}
#[test]
fn is_printable_glyph_accepts_text_rejects_controls() {
assert!(is_printable_glyph('a'));
assert!(is_printable_glyph('Z'));
assert!(is_printable_glyph('9'));
assert!(is_printable_glyph(' '));
assert!(is_printable_glyph('é'));
assert!(!is_printable_glyph('\u{08}')); assert!(!is_printable_glyph('\u{7f}')); assert!(!is_printable_glyph('\n'));
}
#[test]
fn editing_keys_decode_for_captured_key() {
assert_eq!(key_from_vk(VK_BACK), Some(InputKey::Backspace));
assert_eq!(key_from_vk(VK_DELETE), Some(InputKey::Delete));
assert_eq!(key_from_vk(VK_LEFT), Some(InputKey::Left));
assert_eq!(key_from_vk(VK_RIGHT), Some(InputKey::Right));
let mut ks = KeyState::default();
ks.on_key_down(VK_BACK);
assert_eq!(snapshot(&mut ks).captured_key, Some(InputKey::Backspace));
}
}