use std::collections::{HashMap, HashSet};
use sdl2::controller::{Button, Axis};
const ANGLE_UP_RIGHT_RIGHT : f64 = -std::f64::consts::FRAC_PI_8;
const ANGLE_UP_RIGHT : f64 = -std::f64::consts::FRAC_PI_4;
const ANGLE_UP_UP_RIGHT : f64 = -std::f64::consts::FRAC_PI_4 - std::f64::consts::FRAC_PI_8;
const ANGLE_UP_UP_LEFT : f64 = -std::f64::consts::FRAC_PI_2 - std::f64::consts::FRAC_PI_8;
const ANGLE_UP_LEFT : f64 = -std::f64::consts::FRAC_PI_2 - std::f64::consts::FRAC_PI_4;
const ANGLE_UP_LEFT_LEFT : f64 = -std::f64::consts::FRAC_PI_2 - std::f64::consts::FRAC_PI_4 - std::f64::consts::FRAC_PI_8;
const ANGLE_DOWN_LEFT_LEFT : f64 = std::f64::consts::FRAC_PI_2 + std::f64::consts::FRAC_PI_4 + std::f64::consts::FRAC_PI_8;
const ANGLE_DOWN_LEFT : f64 = std::f64::consts::FRAC_PI_2 + std::f64::consts::FRAC_PI_4;
const ANGLE_DOWN_DOWN_LEFT : f64 = std::f64::consts::FRAC_PI_2 + std::f64::consts::FRAC_PI_8;
const ANGLE_DOWN_DOWN_RIGHT : f64 = std::f64::consts::FRAC_PI_4 + std::f64::consts::FRAC_PI_8;
const ANGLE_DOWN_RIGHT : f64 = std::f64::consts::FRAC_PI_4;
const ANGLE_DOWN_RIGHT_RIGHT : f64 = std::f64::consts::FRAC_PI_8;
const DIAGONAL_DEAD_ZONE : f64 = std::f64::consts::FRAC_PI_8 / 2.0;
struct InputAction
{
time_in_state : u64,
held : bool,
old_held : bool,
axis : f64
}
impl InputAction
{
pub fn new() -> InputAction
{
InputAction { time_in_state : 0, held : false, old_held : false, axis : 0.0 }
}
pub fn update_state(&mut self, is_held : bool, axis : Option<f64>)
{
self.old_held = self.held;
if self.held == is_held
{
self.time_in_state += 1;
}
else
{
self.time_in_state = 1;
self.held = is_held
}
if let Some(some_axis) = axis
{
self.axis = some_axis;
}
}
pub fn time_in_state(&self) -> u64
{
self.time_in_state
}
pub fn is_held(&self) -> bool
{
self.held
}
pub fn down_once(&self) -> bool
{
self.held && !self.old_held
}
pub fn up_once(&self) -> bool
{
!self.held && self.old_held
}
pub fn axis(&self) -> f64
{
self.axis
}
}
#[derive(Debug, Clone)]
struct AxisData
{
value : f64,
delta : f64
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputDirection4
{
Left,
Right,
Up,
Down
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputDirection8
{
Left,
UpLeft,
Up,
UpRight,
Right,
DownRight,
Down,
DownLeft
}
pub struct Input
{
keys_held : HashSet<String>,
old_axis_values : HashMap<String, f64>,
axis_values : HashMap<String, AxisData>,
actions_to_keys : HashMap<String, Vec<String>>,
action_map : HashMap<String, InputAction>,
gamepad_to_key : HashMap<Button, String>,
gamepad_to_axis_pos : HashMap<Axis, String>,
gamepad_to_axis_neg : HashMap<Axis, String>,
}
impl Input
{
pub(crate) fn new() -> Input
{
let mut gamepad_to_key = HashMap::new();
gamepad_to_key.insert(Button::A, String::from("gamepad a"));
gamepad_to_key.insert(Button::B, String::from("gamepad b"));
gamepad_to_key.insert(Button::X, String::from("gamepad x"));
gamepad_to_key.insert(Button::Y, String::from("gamepad y"));
gamepad_to_key.insert(Button::Back, String::from("gamepad back"));
gamepad_to_key.insert(Button::Start, String::from("gamepad start"));
gamepad_to_key.insert(Button::LeftStick, String::from("gamepad lstick"));
gamepad_to_key.insert(Button::RightStick, String::from("gamepad rstick"));
gamepad_to_key.insert(Button::LeftShoulder, String::from("gamepad l"));
gamepad_to_key.insert(Button::RightShoulder, String::from("gamepad r"));
gamepad_to_key.insert(Button::DPadLeft, String::from("gamepad left"));
gamepad_to_key.insert(Button::DPadRight, String::from("gamepad right"));
gamepad_to_key.insert(Button::DPadUp, String::from("gamepad up"));
gamepad_to_key.insert(Button::DPadDown, String::from("gamepad down"));
gamepad_to_key.insert(Button::Guide, String::from("gamepad guide"));
let mut gamepad_to_axis_pos = HashMap::new();
let mut gamepad_to_axis_neg = HashMap::new();
gamepad_to_axis_neg.insert(Axis::LeftX, String::from("gamepad lstick left"));
gamepad_to_axis_pos.insert(Axis::LeftX, String::from("gamepad lstick right"));
gamepad_to_axis_neg.insert(Axis::LeftY, String::from("gamepad lstick up"));
gamepad_to_axis_pos.insert(Axis::LeftY, String::from("gamepad lstick down"));
gamepad_to_axis_neg.insert(Axis::RightX, String::from("gamepad rstick left"));
gamepad_to_axis_pos.insert(Axis::RightX, String::from("gamepad rstick right"));
gamepad_to_axis_neg.insert(Axis::RightY, String::from("gamepad rstick up"));
gamepad_to_axis_pos.insert(Axis::RightY, String::from("gamepad rstick down"));
gamepad_to_axis_pos.insert(Axis::TriggerLeft, String::from("gamepad ltrigger"));
gamepad_to_axis_pos.insert(Axis::TriggerRight, String::from("gamepad rtrigger"));
Input
{
keys_held : HashSet::new(),
old_axis_values : HashMap::new(),
axis_values: HashMap::new(),
actions_to_keys : HashMap::new(),
action_map : HashMap::new(),
gamepad_to_key,
gamepad_to_axis_pos,
gamepad_to_axis_neg
}
}
pub fn add_action(&mut self, name : &str)
{
self.action_map.insert(name.to_string(), InputAction::new());
self.actions_to_keys.insert(name.to_string(), Vec::new());
}
pub fn add_bind(&mut self, name : &str, key : &str)
{
if !self.action_map.contains_key(name)
{
self.add_action(name);
}
let lower_key = key.to_lowercase();
if let Some(key_list) = self.actions_to_keys.get_mut(name)
{
key_list.push(lower_key);
}
}
pub fn clear_binds(&mut self, name : &str)
{
if let Some(key_list) = self.actions_to_keys.get_mut(name)
{
key_list.clear();
}
if let Some(action) = self.action_map.get_mut(name)
{
action.axis = 0.0;
}
}
pub fn down_once(&self, name : &str) -> bool
{
let action = try_opt_or_else!(self.action_map.get(name), || false);
action.down_once()
}
pub fn up_once(&self, name : &str) -> bool
{
let action = try_opt_or_else!(self.action_map.get(name), || false);
action.up_once()
}
pub fn held(&self, name : &str) -> bool
{
let action = try_opt_or_else!(self.action_map.get(name), || false);
action.is_held()
}
pub fn time_held(&self, name : &str) -> u64
{
let action = try_opt_or_else!(self.action_map.get(name), || 0);
if action.is_held()
{
return action.time_in_state();
}
0
}
pub fn time_released(&self, name : &str) -> u64
{
let action = try_opt_or_else!(self.action_map.get(name), || 0);
if !action.is_held()
{
return action.time_in_state();
}
0
}
pub fn axis(&self, name : &str) -> f64
{
let action = try_opt_or_else!(self.action_map.get(name), || 0.0);
return action.axis().max(0.0).min(1.0)
}
pub fn paired_axis(&self, name_neg : &str, name_pos : &str) -> f64
{
let neg_value = self.axis(name_neg);
let pos_value = self.axis(name_pos);
pos_value - neg_value
}
pub fn angle_and_distance(&self, name_left : &str, name_right : &str, name_up : &str, name_down : &str) -> (f64, f64)
{
let x = self.paired_axis(name_left, name_right);
let y = self.paired_axis(name_up, name_down);
let angle = y.atan2(x);
let distance = (x.powi(2) + y.powi(2)).sqrt().min(1.0);
(angle, distance)
}
pub fn four_way_direction(&self, name_left : &str, name_right : &str, name_up : &str, name_down : &str) -> Option<InputDirection4>
{
let (angle, distance) = self.angle_and_distance(name_left, name_right, name_up, name_down);
if distance < 0.6
{
return None;
}
if angle < ANGLE_UP_LEFT - DIAGONAL_DEAD_ZONE || angle > ANGLE_DOWN_LEFT + DIAGONAL_DEAD_ZONE
{
return Some(InputDirection4::Left)
}
else if angle > ANGLE_UP_RIGHT + DIAGONAL_DEAD_ZONE && angle < ANGLE_DOWN_RIGHT - DIAGONAL_DEAD_ZONE
{
return Some(InputDirection4::Right)
}
else if angle > ANGLE_UP_LEFT + DIAGONAL_DEAD_ZONE && angle < ANGLE_UP_RIGHT - DIAGONAL_DEAD_ZONE
{
return Some(InputDirection4::Up)
}
else if angle > ANGLE_DOWN_RIGHT + DIAGONAL_DEAD_ZONE && angle < ANGLE_DOWN_LEFT - DIAGONAL_DEAD_ZONE
{
return Some(InputDirection4::Down)
}
None
}
pub fn eight_way_direction(&self, name_left : &str, name_right : &str, name_up : &str, name_down : &str) -> Option<InputDirection8>
{
let (angle, distance) = self.angle_and_distance(name_left, name_right, name_up, name_down);
if distance < 0.6
{
return None;
}
info!("angle {} up-left-left {} down-left-left {}", angle, ANGLE_UP_LEFT_LEFT, ANGLE_DOWN_LEFT_LEFT);
if angle < ANGLE_UP_LEFT_LEFT || angle >= ANGLE_DOWN_LEFT_LEFT
{
return Some(InputDirection8::Left)
}
else if angle >= ANGLE_UP_LEFT_LEFT && angle < ANGLE_UP_UP_LEFT
{
return Some(InputDirection8::UpLeft)
}
else if angle >= ANGLE_UP_UP_LEFT && angle < ANGLE_UP_UP_RIGHT
{
return Some(InputDirection8::Up)
}
else if angle >= ANGLE_UP_UP_RIGHT && angle < ANGLE_UP_RIGHT_RIGHT
{
return Some(InputDirection8::UpRight)
}
else if angle >= ANGLE_UP_RIGHT_RIGHT && angle < ANGLE_DOWN_RIGHT_RIGHT
{
return Some(InputDirection8::Right)
}
else if angle >= ANGLE_DOWN_RIGHT_RIGHT && angle < ANGLE_DOWN_DOWN_RIGHT
{
return Some(InputDirection8::DownRight)
}
else if angle >= ANGLE_DOWN_DOWN_RIGHT && angle < ANGLE_DOWN_DOWN_LEFT
{
return Some(InputDirection8::Down)
}
else if angle >= ANGLE_DOWN_DOWN_LEFT && angle < ANGLE_DOWN_LEFT_LEFT
{
return Some(InputDirection8::DownLeft)
}
return None;
}
pub(crate) fn update_key(&mut self, key : &str, pressed : bool, update_axis : bool)
{
let lower_key = key.to_lowercase();
if pressed
{
self.keys_held.insert(lower_key.clone());
if update_axis
{
self.axis_values.insert(lower_key, AxisData { value: 1.0, delta: 0.5 });
}
}
else
{
self.keys_held.remove(&lower_key);
if update_axis
{
self.axis_values.insert(lower_key, AxisData { value: 0.0, delta: 0.5 });
}
}
}
pub(crate) fn update_axis(&mut self, axis : &str, value : f64)
{
let lower_axis = axis.to_lowercase();
let last_value = self.old_axis_values.get(&lower_axis).cloned().or(Some(0.0)).unwrap();
self.axis_values.insert(lower_axis, AxisData { value, delta : (last_value - value).abs()} );
}
pub(crate) fn update_gamepad_key(&mut self, key : Button, pressed : bool)
{
let gamepad_key_string = self.gamepad_to_key[&key].clone();
self.update_key(&gamepad_key_string, pressed, true)
}
pub(crate) fn update_gamepad_axis(&mut self, axis : Axis, value : i16)
{
if let Some(gamepad_axis_string_neg) = self.gamepad_to_axis_neg.get(&axis).cloned()
{
let split_value = (value as f64 / -32768.0).max(0.0).min(1.0);
self.update_axis(&gamepad_axis_string_neg, split_value);
self.update_key(&gamepad_axis_string_neg, split_value >= 0.6, false);
}
if let Some(gamepad_axis_string_pos) = self.gamepad_to_axis_pos.get(&axis).cloned()
{
let split_value = (value as f64 / 32767.0).max(0.0).min(1.0);
self.update_axis(&gamepad_axis_string_pos, split_value);
self.update_key(&gamepad_axis_string_pos, split_value >= 0.6, false);
}
}
pub(crate) fn update_actions(&mut self)
{
for (name, action) in self.action_map.iter_mut()
{
let mut is_held = false;
let mut axis_value = None;
let mut axis_datas = Vec::new();
for key in &self.actions_to_keys[name]
{
if self.keys_held.contains(key)
{
is_held = true;
}
if let Some(data) = self.axis_values.get(key)
{
axis_datas.push(data.clone());
}
}
if axis_datas.len() > 0
{
axis_datas.sort_by(|a, b| a.delta.partial_cmp(&b.delta).unwrap());
axis_value = Some(axis_datas[axis_datas.len() - 1].value);
}
action.update_state(is_held, axis_value);
}
for (axis, data) in &self.axis_values
{
self.old_axis_values.insert(axis.clone(), data.value);
}
self.axis_values.clear();
}
}