use concinnity_core::components::{InputKey, KeyEvent, KeyMods, KeyPress};
use concinnity_core::input::keymap::KeyMap;
use concinnity_core::input::snapshot::InputSnapshot;
use windows::Win32::UI::Input::KeyboardAndMouse::*;
#[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 shift: bool,
pub events: Vec<KeyEvent>,
pub high_surrogate: Option<u16>,
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;
}
}
}
fn mods(&self) -> KeyMods {
KeyMods {
shift: self.shift,
ctrl: self.ctrl,
alt: self.alt,
cmd: false,
}
}
pub(crate) fn on_key_down(&mut self, vk: VIRTUAL_KEY, repeat: bool) {
if vk == VK_F1 {
self.hud_toggle_pending = true;
}
if vk == VK_CONTROL {
self.ctrl = true;
}
if vk == VK_SHIFT {
self.shift = true;
}
if let Some(key) = key_from_vk(vk) {
self.events.push(KeyEvent::Press(KeyPress {
key,
mods: self.mods(),
repeat,
}));
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;
self.shift = false;
}
pub(crate) fn on_key_up(&mut self, vk: VIRTUAL_KEY) {
if vk == VK_CONTROL {
self.ctrl = false;
}
if vk == VK_SHIFT {
self.shift = false;
}
if let Some(key) = key_from_vk(vk) {
self.apply_binding(key, false, false);
}
}
pub(crate) fn on_char(&mut self, unit: u16) {
let pair = match self.high_surrogate.take() {
Some(high) => [high, unit],
None if (0xD800..0xDC00).contains(&unit) => {
self.high_surrogate = Some(unit);
return;
}
None => [unit, 0],
};
let len = if pair[1] == 0 { 1 } else { 2 };
for c in char::decode_utf16(pair[..len].iter().copied()).flatten() {
if is_printable_glyph(c) {
self.events.push(KeyEvent::Text(c));
}
}
}
pub(crate) fn take(&mut self, mouse: MouseSnapshot) -> InputSnapshot {
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 = InputSnapshot {
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,
key_events: std::mem::take(&mut self.events),
};
self.interact_pending = false;
self.jump_pending = false;
self.hud_toggle_pending = false;
self.escape_pending = false;
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_HOME => InputKey::Home,
VK_END => InputKey::End,
VK_PRIOR => InputKey::PageUp,
VK_NEXT => InputKey::PageDown,
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) -> InputSnapshot {
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, false);
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");
}
fn text(ks: &mut KeyState) -> String {
snapshot(ks)
.key_events
.iter()
.filter_map(|e| match e {
KeyEvent::Text(c) => Some(*c),
KeyEvent::Press(_) => None,
})
.collect()
}
fn unit(c: char) -> u16 {
let mut buf = [0u16; 2];
c.encode_utf16(&mut buf)[0]
}
#[test]
fn typed_glyphs_queue_in_order_and_drain_once() {
let mut ks = KeyState::default();
assert_eq!(text(&mut ks), "", "starts empty");
for c in "Abc".chars() {
ks.on_char(unit(c));
}
assert_eq!(text(&mut ks), "Abc", "every glyph of the frame survives");
assert_eq!(text(&mut ks), "", "drained by take");
}
#[test]
fn surrogate_pairs_join_into_one_char() {
let mut ks = KeyState::default();
let mut buf = [0u16; 2];
for &u in '\u{1F600}'.encode_utf16(&mut buf).iter() {
ks.on_char(u);
}
assert_eq!(text(&mut ks), "\u{1F600}");
}
#[test]
fn control_chars_do_not_become_text() {
let mut ks = KeyState::default();
for c in ['\u{08}', '\r', '\n', '\u{1b}', '\t', '\u{7f}'] {
ks.on_char(unit(c));
}
assert_eq!(text(&mut ks), "", "control chars are not printable glyphs");
}
#[test]
fn presses_record_modifiers_and_repeats_in_order() {
let mut ks = KeyState::default();
ks.on_key_down(VK_CONTROL, false);
ks.on_key_down(VK_Z, false);
ks.on_key_up(VK_CONTROL);
ks.on_key_down(VK_LEFT, false);
ks.on_key_down(VK_LEFT, true);
let presses: Vec<KeyPress> = snapshot(&mut ks)
.key_events
.iter()
.filter_map(|e| match e {
KeyEvent::Press(p) => Some(*p),
KeyEvent::Text(_) => None,
})
.collect();
let keys: Vec<InputKey> = presses.iter().map(|p| p.key).collect();
assert_eq!(keys, [InputKey::Z, InputKey::Left, InputKey::Left]);
assert!(presses[0].mods.ctrl, "Ctrl held for the Z");
assert!(!presses[1].mods.ctrl, "released before the Left");
assert!(!presses[1].repeat && presses[2].repeat);
}
#[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_as_presses() {
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));
assert_eq!(key_from_vk(VK_HOME), Some(InputKey::Home));
assert_eq!(key_from_vk(VK_END), Some(InputKey::End));
assert_eq!(key_from_vk(VK_PRIOR), Some(InputKey::PageUp));
assert_eq!(key_from_vk(VK_NEXT), Some(InputKey::PageDown));
let mut ks = KeyState::default();
ks.on_key_down(VK_BACK, false);
assert_eq!(
snapshot(&mut ks).key_events,
[KeyEvent::Press(KeyPress::new(
InputKey::Backspace,
KeyMods::NONE
))]
);
}
}