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) {
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);
}
}
#[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) {
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 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;
}
use std::collections::HashMap;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum InputBinding {
Key(u32),
MouseButton(u32),
}
#[derive(Clone)]
pub struct ActionMap {
bindings: HashMap<String, Vec<InputBinding>>,
}
impl ActionMap {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
}
}
pub fn bind_key(&mut self, action_name: &str, keycode: u32) {
self.bindings
.entry(action_name.to_string())
.or_default()
.push(InputBinding::Key(keycode));
}
pub fn bind_mouse_button(&mut self, action_name: &str, button: u32) {
self.bindings
.entry(action_name.to_string())
.or_default()
.push(InputBinding::MouseButton(button));
}
pub fn bind_action(&mut self, action_name: &str, keycode: u32) {
self.bind_key(action_name, keycode);
}
pub fn is_action_pressed(&self, input: &Input, action_name: &str) -> bool {
if let Some(bindings) = self.bindings.get(action_name) {
for binding in bindings {
match binding {
InputBinding::Key(k) => {
if input.is_key_pressed(*k) {
return true;
}
}
InputBinding::MouseButton(b) => {
if input.is_mouse_button_pressed(*b) {
return true;
}
}
}
}
}
false
}
pub fn is_action_just_pressed(&self, input: &Input, action_name: &str) -> bool {
if let Some(bindings) = self.bindings.get(action_name) {
for binding in bindings {
match binding {
InputBinding::Key(k) => {
if input.is_key_just_pressed(*k) {
return true;
}
}
InputBinding::MouseButton(b) => {
if input.is_mouse_button_just_pressed(*b) {
return true;
}
}
}
}
}
false
}
pub fn is_action_just_released(&self, input: &Input, action_name: &str) -> bool {
if let Some(bindings) = self.bindings.get(action_name) {
for binding in bindings {
match binding {
InputBinding::Key(k) => {
if input.is_key_just_released(*k) {
return true;
}
}
InputBinding::MouseButton(b) => {
if input.is_mouse_button_just_released(*b) {
return true;
}
}
}
}
}
false
}
}
impl Default for ActionMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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_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)
));
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct FrameRecord {
pub dt: f32,
pub input: Input,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct PlaybackData {
pub frames: Vec<FrameRecord>,
}
impl PlaybackData {
pub fn save(&self, path: &str) -> Result<(), String> {
let string_data = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
.map_err(|e| format!("Serilestirme hatasi: {}", e))?;
std::fs::write(path, string_data).map_err(|e| format!("Dosya yazma hatasi: {}", e))?;
Ok(())
}
pub fn load(path: &str) -> Result<Self, String> {
let string_data =
std::fs::read_to_string(path).map_err(|e| format!("Dosya okuma hatasi: {}", e))?;
ron::from_str(&string_data).map_err(|e| format!("Deserilestirme hatasi: {}", e))
}
}