pub mod keys;
pub use keys::{code_from_name, NAMED_KEYS};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Clone, Serialize, Deserialize)]
pub struct Input {
keys_pressed: HashSet<u32>, keys_just_pressed: HashSet<u32>, keys_just_released: HashSet<u32>,
mouse_buttons_pressed: HashSet<u32>,
mouse_buttons_just_pressed: HashSet<u32>,
mouse_buttons_just_released: HashSet<u32>,
mouse_position: (f32, f32),
mouse_delta: (f32, f32),
mouse_scroll_delta: f32,
}
impl Input {
pub fn new() -> Self {
Self {
keys_pressed: HashSet::new(),
keys_just_pressed: HashSet::new(),
keys_just_released: HashSet::new(),
mouse_buttons_pressed: HashSet::new(),
mouse_buttons_just_pressed: HashSet::new(),
mouse_buttons_just_released: HashSet::new(),
mouse_position: (0.0, 0.0),
mouse_delta: (0.0, 0.0),
mouse_scroll_delta: 0.0,
}
}
pub fn begin_frame(&mut self) {
for k in &self.keys_just_released {
self.keys_pressed.remove(k);
}
for b in &self.mouse_buttons_just_released {
self.mouse_buttons_pressed.remove(b);
}
self.keys_just_pressed.clear();
self.keys_just_released.clear();
self.mouse_buttons_just_pressed.clear();
self.mouse_buttons_just_released.clear();
self.mouse_delta = (0.0, 0.0);
self.mouse_scroll_delta = 0.0;
}
pub fn pressed_keys(&self) -> Vec<u32> {
self.keys_pressed.iter().copied().collect()
}
pub fn on_key_pressed(&mut self, key: u32) {
self.keys_just_released.remove(&key);
if self.keys_pressed.insert(key) {
self.keys_just_pressed.insert(key);
}
}
pub fn on_key_released(&mut self, key: u32) {
self.keys_just_released.insert(key);
if !self.keys_just_pressed.contains(&key) {
self.keys_pressed.remove(&key);
}
}
pub fn release_all(&mut self) {
for k in self.keys_pressed.drain() {
self.keys_just_released.insert(k);
}
for b in self.mouse_buttons_pressed.drain() {
self.mouse_buttons_just_released.insert(b);
}
self.keys_just_pressed.clear();
self.mouse_buttons_just_pressed.clear();
self.mouse_delta = (0.0, 0.0);
self.mouse_scroll_delta = 0.0;
}
#[inline]
pub fn is_key_pressed(&self, key: u32) -> bool {
self.keys_pressed.contains(&key)
}
#[inline]
pub fn is_key_just_pressed(&self, key: u32) -> bool {
self.keys_just_pressed.contains(&key)
}
#[inline]
pub fn is_key_just_released(&self, key: u32) -> bool {
self.keys_just_released.contains(&key)
}
pub fn on_mouse_button_pressed(&mut self, button: u32) {
self.mouse_buttons_just_released.remove(&button);
if self.mouse_buttons_pressed.insert(button) {
self.mouse_buttons_just_pressed.insert(button);
}
}
pub fn on_mouse_button_released(&mut self, button: u32) {
self.mouse_buttons_just_released.insert(button);
if !self.mouse_buttons_just_pressed.contains(&button) {
self.mouse_buttons_pressed.remove(&button);
}
}
#[inline]
pub fn is_mouse_button_pressed(&self, button: u32) -> bool {
self.mouse_buttons_pressed.contains(&button)
}
#[inline]
pub fn is_mouse_button_just_pressed(&self, button: u32) -> bool {
self.mouse_buttons_just_pressed.contains(&button)
}
#[inline]
pub fn is_mouse_button_just_released(&self, button: u32) -> bool {
self.mouse_buttons_just_released.contains(&button)
}
pub fn on_mouse_moved(&mut self, x: f32, y: f32) {
self.mouse_delta.0 += x - self.mouse_position.0;
self.mouse_delta.1 += y - self.mouse_position.1;
self.mouse_position = (x, y);
}
pub fn set_mouse_position(&mut self, x: f32, y: f32) {
self.mouse_position = (x, y);
}
pub fn on_mouse_delta(&mut self, dx: f32, dy: f32) {
self.mouse_delta.0 += dx;
self.mouse_delta.1 += dy;
}
#[inline]
pub fn mouse_position(&self) -> (f32, f32) {
self.mouse_position
}
#[inline]
pub fn mouse_delta(&self) -> (f32, f32) {
self.mouse_delta
}
pub fn on_mouse_scroll(&mut self, delta: f32) {
self.mouse_scroll_delta += delta;
}
#[inline]
pub fn mouse_scroll(&self) -> f32 {
self.mouse_scroll_delta
}
}
impl Default for Input {
fn default() -> Self {
Self::new()
}
}
pub mod mouse {
pub const LEFT: u32 = 0;
pub const RIGHT: u32 = 1;
pub const MIDDLE: u32 = 2;
}
mod fighter;
mod mapping;
pub use fighter::{FighterInputBuffer, FrameActions, FrameRecord, PlaybackData};
pub use mapping::{ActionMap, InputBinding};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fast_tap_release_then_repress_keeps_key_held() {
let mut input = Input::new();
input.on_key_pressed(5);
input.begin_frame(); assert!(input.is_key_pressed(5));
input.on_key_released(5);
input.on_key_pressed(5);
input.begin_frame();
assert!(input.is_key_pressed(5), "re-pressed key must stay held");
assert!(!input.is_key_just_pressed(5), "no spurious just_pressed after begin_frame");
}
#[test]
fn test_fast_tap_preserves_pressed_for_one_frame() {
let mut input = Input::new();
input.on_key_pressed(42);
input.on_key_released(42);
assert!(input.is_key_pressed(42), "fast-tap: tuş pressed olmalı");
assert!(
input.is_key_just_pressed(42),
"fast-tap: tuş just_pressed olmalı"
);
assert!(
input.is_key_just_released(42),
"fast-tap: tuş just_released olmalı"
);
input.begin_frame();
assert!(
!input.is_key_pressed(42),
"sonraki frame: pressed false olmalı"
);
assert!(
!input.is_key_just_pressed(42),
"sonraki frame: just_pressed false olmalı"
);
assert!(
!input.is_key_just_released(42),
"sonraki frame: just_released false olmalı"
);
}
#[test]
fn test_normal_press_release_across_frames() {
let mut input = Input::new();
input.on_key_pressed(10);
assert!(input.is_key_pressed(10));
assert!(input.is_key_just_pressed(10));
input.begin_frame();
assert!(input.is_key_pressed(10));
assert!(!input.is_key_just_pressed(10));
input.on_key_released(10);
assert!(!input.is_key_pressed(10)); assert!(input.is_key_just_released(10));
input.begin_frame();
assert!(!input.is_key_pressed(10));
assert!(!input.is_key_just_released(10));
}
#[test]
fn test_fast_tap_mouse_button() {
let mut input = Input::new();
input.on_mouse_button_pressed(mouse::LEFT);
input.on_mouse_button_released(mouse::LEFT);
assert!(input.is_mouse_button_pressed(mouse::LEFT));
assert!(input.is_mouse_button_just_pressed(mouse::LEFT));
assert!(input.is_mouse_button_just_released(mouse::LEFT));
input.begin_frame();
assert!(!input.is_mouse_button_pressed(mouse::LEFT));
assert!(!input.is_mouse_button_just_pressed(mouse::LEFT));
assert!(!input.is_mouse_button_just_released(mouse::LEFT));
}
#[test]
fn test_mouse_moved_accumulates_delta() {
let mut input = Input::new();
input.on_mouse_moved(100.0, 200.0);
assert_eq!(input.mouse_delta(), (100.0, 200.0));
input.on_mouse_moved(150.0, 250.0);
assert_eq!(input.mouse_delta(), (150.0, 250.0));
assert_eq!(input.mouse_position(), (150.0, 250.0));
}
#[test]
fn test_mouse_delta_resets_on_begin_frame() {
let mut input = Input::new();
input.on_mouse_moved(100.0, 200.0);
assert_ne!(input.mouse_delta(), (0.0, 0.0));
input.begin_frame();
assert_eq!(input.mouse_delta(), (0.0, 0.0));
assert_eq!(input.mouse_position(), (100.0, 200.0));
}
#[test]
fn test_release_all_clears_held_keys_and_buttons() {
let mut input = Input::new();
input.on_key_pressed(65); input.on_key_pressed(87); input.on_mouse_button_pressed(1);
input.on_mouse_moved(10.0, 10.0);
input.begin_frame(); assert!(input.is_key_pressed(65));
assert!(input.is_key_pressed(87));
assert!(input.is_mouse_button_pressed(1));
input.release_all();
assert!(!input.is_key_pressed(65), "A odak kaybından sonra hâlâ basılı");
assert!(!input.is_key_pressed(87), "W odak kaybından sonra hâlâ basılı");
assert!(!input.is_mouse_button_pressed(1));
assert_eq!(input.mouse_delta(), (0.0, 0.0));
assert!(input.is_key_just_released(65));
}
#[test]
fn release_all_cancels_pending_just_pressed_edges() {
let mut input = Input::new();
input.on_key_pressed(32); input.on_mouse_button_pressed(mouse::LEFT);
assert!(input.is_key_just_pressed(32));
assert!(input.is_mouse_button_just_pressed(mouse::LEFT));
input.release_all();
assert!(
!input.is_key_just_pressed(32),
"odak kaybı bekleyen basma kenarını iptal etmeli"
);
assert!(
!input.is_mouse_button_just_pressed(mouse::LEFT),
"fare düğmesi için de aynısı"
);
assert!(!input.is_key_pressed(32));
assert!(input.is_key_just_released(32));
assert!(input.is_mouse_button_just_released(mouse::LEFT));
input.on_key_pressed(32);
assert!(input.is_key_pressed(32) && input.is_key_just_pressed(32));
}
#[test]
fn test_scroll_accumulates_and_resets() {
let mut input = Input::new();
input.on_mouse_scroll(3.0);
input.on_mouse_scroll(-1.0);
assert_eq!(input.mouse_scroll(), 2.0);
input.begin_frame();
assert_eq!(input.mouse_scroll(), 0.0);
}
#[test]
fn test_pressed_keys() {
let mut input = Input::new();
input.on_key_pressed(1);
input.on_key_pressed(2);
input.on_key_pressed(3);
let mut keys = input.pressed_keys();
keys.sort();
assert_eq!(keys, vec![1, 2, 3]);
}
#[test]
fn test_action_map_key_binding() {
let mut input = Input::new();
let mut actions = ActionMap::new();
actions.bind_key("Jump", 42);
input.on_key_pressed(42);
assert!(actions.is_action_pressed(&input, "Jump"));
assert!(actions.is_action_just_pressed(&input, "Jump"));
}
#[test]
fn test_action_map_mouse_binding() {
let mut input = Input::new();
let mut actions = ActionMap::new();
actions.bind_mouse_button("Attack", mouse::LEFT);
input.on_mouse_button_pressed(mouse::LEFT);
assert!(actions.is_action_pressed(&input, "Attack"));
assert!(actions.is_action_just_pressed(&input, "Attack"));
input.begin_frame();
input.on_mouse_button_released(mouse::LEFT);
assert!(actions.is_action_just_released(&input, "Attack"));
}
#[test]
fn test_action_map_mixed_bindings() {
let mut input = Input::new();
let mut actions = ActionMap::new();
actions.bind_key("Fire", 42);
actions.bind_mouse_button("Fire", mouse::LEFT);
assert!(!actions.is_action_pressed(&input, "Fire"));
input.on_mouse_button_pressed(mouse::LEFT);
assert!(actions.is_action_pressed(&input, "Fire"));
input.begin_frame();
input.on_mouse_button_released(mouse::LEFT);
input.on_key_pressed(42);
assert!(actions.is_action_pressed(&input, "Fire"));
}
#[test]
fn test_action_map_just_released() {
let mut input = Input::new();
let mut actions = ActionMap::new();
actions.bind_key("Charge", 99);
input.on_key_pressed(99);
input.begin_frame();
input.on_key_released(99);
assert!(actions.is_action_just_released(&input, "Charge"));
assert!(!actions.is_action_pressed(&input, "Charge"));
}
#[test]
fn test_bind_action_backward_compat() {
let mut actions = ActionMap::new();
actions.bind_action("Jump", 42); assert!(matches!(
actions.bindings.get("Jump").unwrap()[0],
InputBinding::Key(42)
));
}
}