doukutsu-rs/src/ggez/input/keyboard.rs

414 lines
14 KiB
Rust

//! Keyboard utility functions; allow querying state of keyboard keys and modifiers.
//!
//! Example:
//!
//! ```rust, compile
//! use ggez::event::{self, EventHandler, KeyCode, KeyMods};
//! use ggez::{graphics, nalgebra as na, timer};
//! use ggez::input::keyboard;
//! use ggez::{Context, GameResult};
//!
//! struct MainState {
//! position_x: f32,
//! }
//!
//! impl EventHandler for MainState {
//! fn update(&mut self, ctx: &mut Context) -> GameResult {
//! // Increase or decrease `position_x` by 0.5, or by 5.0 if Shift is held.
//! if keyboard::is_key_pressed(ctx, KeyCode::Right) {
//! if keyboard::is_mod_active(ctx, KeyMods::SHIFT) {
//! self.position_x += 4.5;
//! }
//! self.position_x += 0.5;
//! } else if keyboard::is_key_pressed(ctx, KeyCode::Left) {
//! if keyboard::is_mod_active(ctx, KeyMods::SHIFT) {
//! self.position_x -= 4.5;
//! }
//! self.position_x -= 0.5;
//! }
//! Ok(())
//! }
//!
//! fn draw(&mut self, ctx: &mut Context) -> GameResult {
//! graphics::clear(ctx, [0.1, 0.2, 0.3, 1.0].into());
//! // Create a circle at `position_x` and draw
//! let circle = graphics::Mesh::new_circle(
//! ctx,
//! graphics::DrawMode::fill(),
//! na::Point2::new(self.position_x, 380.0),
//! 100.0,
//! 2.0,
//! graphics::WHITE,
//! )?;
//! graphics::draw(ctx, &circle, graphics::DrawParam::default())?;
//! graphics::present(ctx)?;
//! timer::yield_now();
//! Ok(())
//! }
//!
//! fn key_down_event(&mut self, ctx: &mut Context, key: KeyCode, mods: KeyMods, _: bool) {
//! match key {
//! // Quit if Shift+Ctrl+Q is pressed.
//! KeyCode::Q => {
//! if mods.contains(KeyMods::SHIFT & KeyMods::CTRL) {
//! println!("Terminating!");
//! event::quit(ctx);
//! } else if mods.contains(KeyMods::SHIFT) || mods.contains(KeyMods::CTRL) {
//! println!("You need to hold both Shift and Control to quit.");
//! } else {
//! println!("Now you're not even trying!");
//! }
//! }
//! _ => (),
//! }
//! }
//! }
//! ```
use crate::ggez::context::Context;
use std::collections::HashSet;
use winit::event::ModifiersState;
/// A key code.
pub use winit::event::VirtualKeyCode as KeyCode;
bitflags! {
/// Bitflags describing the state of keyboard modifiers, such as `Control` or `Shift`.
#[derive(Default)]
pub struct KeyMods: u8 {
/// No modifiers; equivalent to `KeyMods::default()` and
/// [`KeyMods::empty()`](struct.KeyMods.html#method.empty).
const NONE = 0b0000_0000;
/// Left or right Shift key.
const SHIFT = 0b0000_0001;
/// Left or right Control key.
const CTRL = 0b0000_0010;
/// Left or right Alt key.
const ALT = 0b0000_0100;
/// Left or right Win/Cmd/equivalent key.
const LOGO = 0b0000_1000;
}
}
impl From<ModifiersState> for KeyMods {
fn from(state: ModifiersState) -> Self {
let mut keymod = KeyMods::empty();
if state.shift() {
keymod |= Self::SHIFT;
}
if state.ctrl() {
keymod |= Self::CTRL;
}
if state.alt() {
keymod |= Self::ALT;
}
if state.logo() {
keymod |= Self::LOGO;
}
keymod
}
}
/// Tracks held down keyboard keys, active keyboard modifiers,
/// and figures out if the system is sending repeat keystrokes.
#[derive(Clone, Debug)]
pub struct KeyboardContext {
active_modifiers: KeyMods,
/// A simple mapping of which key code has been pressed.
/// We COULD use a `Vec<bool>` but turning Rust enums to and from
/// integers is unsafe and a set really is what we want anyway.
pressed_keys_set: HashSet<KeyCode>,
// These two are necessary for tracking key-repeat.
last_pressed: Option<KeyCode>,
current_pressed: Option<KeyCode>,
}
impl KeyboardContext {
pub(crate) fn new() -> Self {
Self {
active_modifiers: KeyMods::empty(),
// We just use 256 as a number Big Enough For Keyboard Keys to try to avoid resizing.
pressed_keys_set: HashSet::with_capacity(256),
last_pressed: None,
current_pressed: None,
}
}
pub(crate) fn set_key(&mut self, key: KeyCode, pressed: bool) {
if pressed {
let _ = self.pressed_keys_set.insert(key);
self.last_pressed = self.current_pressed;
self.current_pressed = Some(key);
} else {
let _ = self.pressed_keys_set.remove(&key);
self.current_pressed = None;
}
self.set_key_modifier(key, pressed);
}
/// Take a modifier key code and alter our state.
///
/// Double check that this edge handling is necessary;
/// winit sounds like it should do this for us,
/// see https://docs.rs/winit/0.18.0/winit/struct.KeyboardInput.html#structfield.modifiers
///
/// ...more specifically, we should refactor all this to consistant-ify events a bit and
/// make winit do more of the work.
/// But to quote Scott Pilgrim, "This is... this is... Booooooring."
fn set_key_modifier(&mut self, key: KeyCode, pressed: bool) {
if pressed {
match key {
KeyCode::LShift | KeyCode::RShift => self.active_modifiers |= KeyMods::SHIFT,
KeyCode::LControl | KeyCode::RControl => self.active_modifiers |= KeyMods::CTRL,
KeyCode::LAlt | KeyCode::RAlt => self.active_modifiers |= KeyMods::ALT,
KeyCode::LWin | KeyCode::RWin => self.active_modifiers |= KeyMods::LOGO,
_ => (),
}
} else {
match key {
KeyCode::LShift | KeyCode::RShift => self.active_modifiers -= KeyMods::SHIFT,
KeyCode::LControl | KeyCode::RControl => self.active_modifiers -= KeyMods::CTRL,
KeyCode::LAlt | KeyCode::RAlt => self.active_modifiers -= KeyMods::ALT,
KeyCode::LWin | KeyCode::RWin => self.active_modifiers -= KeyMods::LOGO,
_ => (),
}
}
}
pub(crate) fn set_modifiers(&mut self, keymods: KeyMods) {
self.active_modifiers = keymods;
}
pub(crate) fn is_key_pressed(&self, key: KeyCode) -> bool {
self.pressed_keys_set.contains(&key)
}
pub(crate) fn is_key_repeated(&self) -> bool {
if self.last_pressed.is_some() {
self.last_pressed == self.current_pressed
} else {
false
}
}
pub(crate) fn pressed_keys(&self) -> &HashSet<KeyCode> {
&self.pressed_keys_set
}
pub(crate) fn active_mods(&self) -> KeyMods {
self.active_modifiers
}
}
impl Default for KeyboardContext {
fn default() -> Self {
Self::new()
}
}
/// Checks if a key is currently pressed down.
pub fn is_key_pressed(ctx: &Context, key: KeyCode) -> bool {
ctx.keyboard_context.is_key_pressed(key)
}
/// Checks if the last keystroke sent by the system is repeated,
/// like when a key is held down for a period of time.
pub fn is_key_repeated(ctx: &Context) -> bool {
ctx.keyboard_context.is_key_repeated()
}
/// Returns a reference to the set of currently pressed keys.
pub fn pressed_keys(ctx: &Context) -> &HashSet<KeyCode> {
ctx.keyboard_context.pressed_keys()
}
/// Checks if keyboard modifier (or several) is active.
pub fn is_mod_active(ctx: &Context, keymods: KeyMods) -> bool {
ctx.keyboard_context.active_mods().contains(keymods)
}
/// Returns currently active keyboard modifiers.
pub fn active_mods(ctx: &Context) -> KeyMods {
ctx.keyboard_context.active_mods()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_mod_conversions() {
assert_eq!(
KeyMods::empty(),
KeyMods::from(ModifiersState {
shift: false,
ctrl: false,
alt: false,
logo: false,
})
);
assert_eq!(
KeyMods::SHIFT,
KeyMods::from(ModifiersState {
shift: true,
ctrl: false,
alt: false,
logo: false,
})
);
assert_eq!(
KeyMods::SHIFT | KeyMods::ALT,
KeyMods::from(ModifiersState {
shift: true,
ctrl: false,
alt: true,
logo: false,
})
);
assert_eq!(
KeyMods::SHIFT | KeyMods::ALT | KeyMods::CTRL,
KeyMods::from(ModifiersState {
shift: true,
ctrl: true,
alt: true,
logo: false,
})
);
assert_eq!(
KeyMods::SHIFT - KeyMods::ALT,
KeyMods::from(ModifiersState {
shift: true,
ctrl: false,
alt: false,
logo: false,
})
);
assert_eq!(
(KeyMods::SHIFT | KeyMods::ALT) - KeyMods::ALT,
KeyMods::from(ModifiersState {
shift: true,
ctrl: false,
alt: false,
logo: false,
})
);
assert_eq!(
KeyMods::SHIFT - (KeyMods::ALT | KeyMods::SHIFT),
KeyMods::from(ModifiersState {
shift: false,
ctrl: false,
alt: false,
logo: false,
})
);
}
#[test]
fn pressed_keys_tracking() {
let mut keyboard = KeyboardContext::new();
assert_eq!(keyboard.pressed_keys(), &[].iter().cloned().collect());
assert!(!keyboard.is_key_pressed(KeyCode::A));
keyboard.set_key(KeyCode::A, true);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::A].iter().cloned().collect()
);
assert!(keyboard.is_key_pressed(KeyCode::A));
keyboard.set_key(KeyCode::A, false);
assert_eq!(keyboard.pressed_keys(), &[].iter().cloned().collect());
assert!(!keyboard.is_key_pressed(KeyCode::A));
keyboard.set_key(KeyCode::A, true);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::A].iter().cloned().collect()
);
assert!(keyboard.is_key_pressed(KeyCode::A));
keyboard.set_key(KeyCode::A, true);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::A].iter().cloned().collect()
);
keyboard.set_key(KeyCode::B, true);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::A, KeyCode::B].iter().cloned().collect()
);
keyboard.set_key(KeyCode::B, true);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::A, KeyCode::B].iter().cloned().collect()
);
keyboard.set_key(KeyCode::A, false);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::B].iter().cloned().collect()
);
keyboard.set_key(KeyCode::A, false);
assert_eq!(
keyboard.pressed_keys(),
&[KeyCode::B].iter().cloned().collect()
);
keyboard.set_key(KeyCode::B, false);
assert_eq!(keyboard.pressed_keys(), &[].iter().cloned().collect());
}
#[test]
fn keyboard_modifiers() {
let mut keyboard = KeyboardContext::new();
// this test is mostly useless and is primarily for code coverage
assert_eq!(keyboard.active_mods(), KeyMods::default());
keyboard.set_modifiers(KeyMods::from(ModifiersState {
shift: true,
ctrl: true,
alt: true,
logo: true,
}));
// these test the workaround for https://github.com/tomaka/winit/issues/600
assert_eq!(
keyboard.active_mods(),
KeyMods::SHIFT | KeyMods::CTRL | KeyMods::ALT | KeyMods::LOGO
);
keyboard.set_key(KeyCode::LControl, false);
assert_eq!(
keyboard.active_mods(),
KeyMods::SHIFT | KeyMods::ALT | KeyMods::LOGO
);
keyboard.set_key(KeyCode::RAlt, false);
assert_eq!(keyboard.active_mods(), KeyMods::SHIFT | KeyMods::LOGO);
keyboard.set_key(KeyCode::LWin, false);
assert_eq!(keyboard.active_mods(), KeyMods::SHIFT);
}
#[test]
fn repeated_keys_tracking() {
let mut keyboard = KeyboardContext::new();
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, true);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, false);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, true);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, true);
assert_eq!(keyboard.is_key_repeated(), true);
keyboard.set_key(KeyCode::A, false);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, true);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::B, true);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, true);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::A, true);
assert_eq!(keyboard.is_key_repeated(), true);
keyboard.set_key(KeyCode::B, true);
assert_eq!(keyboard.is_key_repeated(), false);
keyboard.set_key(KeyCode::B, true);
assert_eq!(keyboard.is_key_repeated(), true);
}
}