use std::{
borrow::Borrow,
error::Error,
fmt::{Debug, Display, Formatter, Result as FmtResult},
hash::Hash,
};
use derivative::Derivative;
use fnv::FnvHashMap as HashMap;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use super::{Axis, Button};
pub trait BindingTypes: Debug + Send + Sync + 'static {
type Axis: Clone + Debug + Hash + Eq + Send + Sync + 'static;
type Action: Clone + Debug + Hash + Eq + Send + Sync + 'static;
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct StringBindings;
impl BindingTypes for StringBindings {
type Axis = String;
type Action = String;
}
#[derive(Derivative, Serialize, Deserialize)]
#[derivative(Debug(bound = ""), Default(bound = ""), Clone(bound = ""))]
#[serde(bound(
serialize = "T::Axis: Serialize, T::Action: Serialize",
deserialize = "T::Axis: Deserialize<'de>, T::Action: Deserialize<'de>",
))]
pub struct Bindings<T: BindingTypes> {
pub(super) axes: HashMap<T::Axis, Axis>,
pub(super) actions: HashMap<T::Action, SmallVec<[SmallVec<[Button; 2]>; 4]>>,
}
#[derive(Clone, Derivative)]
#[derivative(Debug(bound = ""))]
pub enum BindingError<T: BindingTypes> {
MouseWheelAxisAlreadyBound(T::Axis),
ComboContainsDuplicates(T::Action),
ComboAlreadyBound(T::Action),
ButtonBoundToAxis(T::Axis, Axis),
AxisButtonAlreadyBoundToAxis(T::Axis, Axis),
AxisButtonAlreadyBoundToAction(T::Action, Button),
ControllerAxisAlreadyBound(T::Axis),
}
impl<T: BindingTypes> PartialEq for BindingError<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
BindingError::ComboContainsDuplicates(a),
BindingError::ComboContainsDuplicates(x),
) => a == x,
(BindingError::ComboAlreadyBound(a), BindingError::ComboAlreadyBound(x)) => a == x,
(BindingError::ButtonBoundToAxis(a, b), BindingError::ButtonBoundToAxis(x, y)) => {
a == x && b == y
}
(
BindingError::AxisButtonAlreadyBoundToAxis(a, b),
BindingError::AxisButtonAlreadyBoundToAxis(x, y),
) => a == x && b == y,
(
BindingError::AxisButtonAlreadyBoundToAction(a, b),
BindingError::AxisButtonAlreadyBoundToAction(x, y),
) => a == x && b == y,
(
BindingError::ControllerAxisAlreadyBound(a),
BindingError::ControllerAxisAlreadyBound(x),
) => a == x,
(
BindingError::MouseWheelAxisAlreadyBound(a),
BindingError::MouseWheelAxisAlreadyBound(x),
) => a == x,
(_, _) => false,
}
}
}
impl<T: BindingTypes> Display for BindingError<T>
where
T::Action: Display,
T::Axis: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
BindingError::ComboContainsDuplicates(ref id) => write!(
f,
"Combo provided contained two (or more) of the same button: {}",
id
),
BindingError::ComboAlreadyBound(ref action) => {
write!(f, "Combo provided was already bound to action {}", action)
}
BindingError::ButtonBoundToAxis(ref id, ref _axis) => {
write!(f, "Button provided was a button in use by axis {}", id)
}
BindingError::AxisButtonAlreadyBoundToAxis(ref id, ref _axis) => write!(
f,
"Axis provided contained a button that's already in use by axis {}",
id
),
BindingError::AxisButtonAlreadyBoundToAction(ref id, ref _action) => write!(
f,
"Axis provided contained a button that's already in use by single button action {}",
id
),
BindingError::ControllerAxisAlreadyBound(ref id) => {
write!(f, "Controller axis provided is already in use by {}", id)
}
BindingError::MouseWheelAxisAlreadyBound(ref id) => {
write!(f, "Mouse wheel axis provided is already in use by {}", id)
}
}
}
}
impl<T: BindingTypes> Error for BindingError<T>
where
T::Action: Display,
T::Axis: Display,
{
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActionRemovedError {
ActionExistsButBindingDoesnt,
ActionNotFound,
BindingContainsDuplicates,
}
impl Display for ActionRemovedError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
ActionRemovedError::ActionExistsButBindingDoesnt => {
write!(f, "Action found, but binding isn't present")
}
ActionRemovedError::ActionNotFound => write!(f, "Action not found"),
ActionRemovedError::BindingContainsDuplicates => {
write!(f, "Binding removal requested contains duplicate buttons")
}
}
}
}
impl Error for ActionRemovedError {}
impl<T: BindingTypes> Bindings<T> {
pub fn new() -> Self {
Default::default()
}
}
impl<T: BindingTypes> Bindings<T> {
pub fn insert_axis<A: Into<T::Axis>>(
&mut self,
id: A,
axis: Axis,
) -> Result<Option<Axis>, BindingError<T>> {
let id = id.into();
self.check_axis_invariants(&id, &axis)?;
Ok(self.axes.insert(id, axis))
}
pub fn remove_axis<A>(&mut self, id: &A) -> Option<Axis>
where
T::Axis: Borrow<A>,
A: Hash + Eq + ?Sized,
{
self.axes.remove(id)
}
pub fn axis<A>(&self, id: &A) -> Option<&Axis>
where
T::Axis: Borrow<A>,
A: Hash + Eq + ?Sized,
{
self.axes.get(id)
}
pub fn axes(&self) -> impl Iterator<Item = &T::Axis> {
self.axes.keys()
}
pub fn insert_action_binding<B: IntoIterator<Item = Button>>(
&mut self,
id: T::Action,
binding: B,
) -> Result<(), BindingError<T>> {
let bind: SmallVec<[Button; 2]> = binding.into_iter().collect();
self.check_action_invariants(&id, bind.as_slice())?;
let mut make_new = false;
match self.actions.get_mut(&id) {
Some(action_bindings) => {
action_bindings.push(bind.clone());
}
None => {
make_new = true;
}
}
if make_new {
let mut bindings = SmallVec::new();
bindings.push(bind.clone());
self.actions.insert(id, bindings);
}
Ok(())
}
pub fn remove_action_binding<A>(
&mut self,
id: &A,
binding: &[Button],
) -> Result<(), ActionRemovedError>
where
T::Action: Borrow<A>,
A: Hash + Eq + ?Sized,
{
for i in 0..binding.len() {
for j in (i + 1)..binding.len() {
if binding[i] == binding[j] {
return Err(ActionRemovedError::BindingContainsDuplicates);
}
}
}
let kill_it;
if let Some(action_bindings) = self.actions.get_mut(id) {
let index = action_bindings.iter().position(|b| {
b.len() == binding.len()
&& b.iter().all(|b| binding.iter().any(|binding| b == binding))
});
if let Some(index) = index {
action_bindings.swap_remove(index);
} else {
return Err(ActionRemovedError::ActionExistsButBindingDoesnt);
}
kill_it = action_bindings.is_empty();
} else {
return Err(ActionRemovedError::ActionNotFound);
}
if kill_it {
self.actions.remove(id);
}
Ok(())
}
pub fn action_bindings<A>(&self, id: &A) -> impl Iterator<Item = &[Button]>
where
T::Action: Borrow<A>,
A: Hash + Eq + ?Sized,
{
self.actions
.get(id)
.map(SmallVec::as_slice)
.unwrap_or(&[])
.iter()
.map(SmallVec::as_slice)
}
pub fn actions(&self) -> impl Iterator<Item = &T::Action> {
self.actions.keys()
}
pub fn check_invariants(&mut self) -> Result<(), BindingError<T>> {
let action_bindings = self
.actions
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<_>>();
for (k, v) in action_bindings {
for c in v {
self.remove_action_binding(&k, &c)
.expect("Unreachable: We just cloned the bindings, they can't be incorrect.");
self.insert_action_binding(k.clone(), c)?;
}
}
let axis_bindings = self
.axes
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<_>>();
for (k, a) in axis_bindings {
self.remove_axis(&k);
self.insert_axis(k, a)?;
}
Ok(())
}
fn check_action_invariants(
&self,
id: &T::Action,
bind: &[Button],
) -> Result<(), BindingError<T>> {
for i in 0..bind.len() {
for j in (i + 1)..bind.len() {
if bind[i] == bind[j] {
return Err(BindingError::ComboContainsDuplicates(id.clone()));
}
}
}
if bind.len() == 1 {
for (k, a) in self.axes.iter() {
if let Axis::Emulated { pos, neg } = a {
if bind[0] == *pos || bind[0] == *neg {
return Err(BindingError::ButtonBoundToAxis(k.clone(), a.clone()));
}
}
}
}
for (k, a) in self.actions.iter() {
for c in a {
if c.len() == bind.len() && bind.iter().all(|bind| c.iter().any(|c| c == bind)) {
return Err(BindingError::ComboAlreadyBound(k.clone()));
}
}
}
Ok(())
}
fn check_axis_invariants(&self, id: &T::Axis, axis: &Axis) -> Result<(), BindingError<T>> {
match axis {
Axis::Emulated {
pos: ref axis_pos,
neg: ref axis_neg,
} => {
for (k, a) in self.axes.iter().filter(|(k, _a)| *k != id) {
if let Axis::Emulated { pos, neg } = a {
if axis_pos == pos || axis_pos == neg || axis_neg == pos || axis_neg == neg
{
return Err(BindingError::AxisButtonAlreadyBoundToAxis(
k.clone(),
a.clone(),
));
}
}
}
for (k, a) in self.actions.iter() {
for c in a {
if c.len() == 1 && (c[0] == *axis_pos || c[0] == *axis_neg) {
return Err(BindingError::AxisButtonAlreadyBoundToAction(
k.clone(),
c[0],
));
}
}
}
}
Axis::Controller {
controller_id: ref input_controller_id,
axis: ref input_axis,
..
} => {
for (k, a) in self.axes.iter().filter(|(k, _a)| *k != id) {
if let Axis::Controller {
controller_id,
axis,
..
} = a
{
if controller_id == input_controller_id && axis == input_axis {
return Err(BindingError::ControllerAxisAlreadyBound(k.clone()));
}
}
}
}
Axis::MouseWheel {
horizontal: ref input_horizontal,
} => {
for (k, a) in self.axes.iter().filter(|(k, _a)| *k != id) {
if let Axis::MouseWheel { horizontal } = a {
if input_horizontal == horizontal {
return Err(BindingError::MouseWheelAxisAlreadyBound(k.clone()));
}
}
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{button::*, controller::ControllerAxis};
use winit::{MouseButton, VirtualKeyCode};
#[test]
fn add_and_remove_actions() {
let mut bindings = Bindings::<StringBindings>::new();
assert_eq!(bindings.actions().next(), None);
assert_eq!(bindings.action_bindings("test_action").next(), None);
bindings
.insert_action_binding(
String::from("test_action"),
[Button::Mouse(MouseButton::Left)].iter().cloned(),
)
.unwrap();
assert_eq!(
bindings.actions().collect::<Vec<_>>(),
vec![&String::from("test_action")]
);
let action_bindings = bindings.action_bindings("test_action").collect::<Vec<_>>();
assert_eq!(action_bindings, vec![[Button::Mouse(MouseButton::Left)]]);
bindings
.remove_action_binding("test_action", &[Button::Mouse(MouseButton::Left)])
.unwrap();
assert_eq!(bindings.actions().next(), None);
assert_eq!(bindings.action_bindings("test_action").next(), None);
bindings
.insert_action_binding(
String::from("test_action"),
[
Button::Mouse(MouseButton::Left),
Button::Mouse(MouseButton::Right),
]
.iter()
.cloned(),
)
.unwrap();
assert_eq!(
bindings.actions().collect::<Vec<_>>(),
vec![&String::from("test_action")],
);
let action_bindings = bindings.action_bindings("test_action").collect::<Vec<_>>();
assert_eq!(
action_bindings,
vec![[
Button::Mouse(MouseButton::Left),
Button::Mouse(MouseButton::Right)
]]
);
bindings
.remove_action_binding(
"test_action",
&[
Button::Mouse(MouseButton::Left),
Button::Mouse(MouseButton::Right),
],
)
.unwrap();
assert_eq!(bindings.actions().next(), None);
assert_eq!(bindings.action_bindings("test_action").next(), None);
bindings
.insert_action_binding(
String::from("test_action"),
[
Button::Mouse(MouseButton::Left),
Button::Mouse(MouseButton::Right),
]
.iter()
.cloned(),
)
.unwrap();
assert_eq!(
bindings
.remove_action_binding(
"test_action",
&[
Button::Mouse(MouseButton::Right),
Button::Mouse(MouseButton::Right),
],
)
.unwrap_err(),
ActionRemovedError::BindingContainsDuplicates
);
assert_eq!(
bindings
.remove_action_binding("test_action", &[Button::Mouse(MouseButton::Left),],)
.unwrap_err(),
ActionRemovedError::ActionExistsButBindingDoesnt
);
assert_eq!(
bindings
.remove_action_binding("nonsense_action", &[Button::Mouse(MouseButton::Left),],)
.unwrap_err(),
ActionRemovedError::ActionNotFound
);
let actions = bindings.actions().collect::<Vec<_>>();
assert_eq!(actions, vec![&String::from("test_action")]);
let action_bindings = bindings.action_bindings("test_action").collect::<Vec<_>>();
assert_eq!(
action_bindings,
vec![[
Button::Mouse(MouseButton::Left),
Button::Mouse(MouseButton::Right)
]]
);
bindings
.remove_action_binding(
"test_action",
&[
Button::Mouse(MouseButton::Right),
Button::Mouse(MouseButton::Left),
],
)
.unwrap();
assert_eq!(bindings.actions().next(), None);
assert_eq!(bindings.action_bindings("test_action").next(), None);
}
#[test]
fn insert_errors() {
let mut bindings = Bindings::<StringBindings>::new();
assert_eq!(
bindings
.insert_action_binding(
String::from("test_action"),
[
Button::Mouse(MouseButton::Left),
Button::Mouse(MouseButton::Right),
Button::Mouse(MouseButton::Left),
]
.iter()
.cloned(),
)
.unwrap_err(),
BindingError::ComboContainsDuplicates(String::from("test_action"))
);
bindings
.insert_action_binding(
String::from("test_action"),
[Button::Mouse(MouseButton::Left)].iter().cloned(),
)
.unwrap();
assert_eq!(
bindings
.insert_action_binding(
String::from("test_action"),
[Button::Mouse(MouseButton::Left),].iter().cloned(),
)
.unwrap_err(),
BindingError::ComboAlreadyBound(String::from("test_action"))
);
assert_eq!(
bindings
.insert_action_binding(
String::from("test_action_2"),
[Button::Mouse(MouseButton::Left),].iter().cloned(),
)
.unwrap_err(),
BindingError::ComboAlreadyBound(String::from("test_action"))
);
assert_eq!(
bindings
.insert_axis(
String::from("test_axis"),
Axis::Emulated {
pos: Button::Mouse(MouseButton::Left),
neg: Button::Mouse(MouseButton::Right),
},
)
.unwrap_err(),
BindingError::AxisButtonAlreadyBoundToAction(
String::from("test_action"),
Button::Mouse(MouseButton::Left)
)
);
assert_eq!(
bindings
.insert_axis(
String::from("test_axis"),
Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Right),
},
)
.unwrap(),
None
);
assert_eq!(
bindings
.insert_action_binding(
String::from("test_action_2"),
[Button::Key(VirtualKeyCode::Left),].iter().cloned(),
)
.unwrap_err(),
BindingError::ButtonBoundToAxis(
String::from("test_axis"),
Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Right),
}
)
);
assert_eq!(
bindings
.insert_action_binding(
String::from("test_axis_2"),
[Button::Key(VirtualKeyCode::Left),].iter().cloned(),
)
.unwrap_err(),
BindingError::ButtonBoundToAxis(
String::from("test_axis"),
Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Right),
}
)
);
assert_eq!(
bindings
.insert_axis(
String::from("test_axis_2"),
Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Up),
},
)
.unwrap_err(),
BindingError::AxisButtonAlreadyBoundToAxis(
String::from("test_axis"),
Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Right),
}
)
);
assert_eq!(
bindings
.insert_axis(
String::from("test_controller_axis"),
Axis::Controller {
controller_id: 0,
axis: ControllerAxis::RightX,
invert: false,
dead_zone: 0.25,
},
)
.unwrap(),
None
);
assert_eq!(
bindings
.insert_axis(
String::from("test_controller_axis"),
Axis::Controller {
controller_id: 0,
axis: ControllerAxis::LeftX,
invert: false,
dead_zone: 0.25,
},
)
.unwrap(),
Some(Axis::Controller {
controller_id: 0,
axis: ControllerAxis::RightX,
invert: false,
dead_zone: 0.25,
})
);
assert_eq!(
bindings
.insert_axis(
String::from("test_controller_axis_2"),
Axis::Controller {
controller_id: 0,
axis: ControllerAxis::LeftX,
invert: true,
dead_zone: 0.1,
},
)
.unwrap_err(),
BindingError::ControllerAxisAlreadyBound(String::from("test_controller_axis"))
);
assert_eq!(
bindings
.insert_axis(
String::from("test_mouse_wheel_axis"),
Axis::MouseWheel { horizontal: true },
)
.unwrap(),
None
);
assert_eq!(
bindings
.insert_axis(
String::from("test_mouse_wheel_axis"),
Axis::MouseWheel { horizontal: false },
)
.unwrap(),
Some(Axis::MouseWheel { horizontal: true })
);
assert_eq!(
bindings
.insert_axis(
String::from("test_mouse_wheel_axis_2"),
Axis::MouseWheel { horizontal: false },
)
.unwrap_err(),
BindingError::MouseWheelAxisAlreadyBound(String::from("test_mouse_wheel_axis"))
);
}
#[test]
fn add_and_remove_axes() {
let mut bindings = Bindings::<StringBindings>::new();
assert_eq!(
bindings
.insert_axis(
String::from("test_axis"),
Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Right),
},
)
.unwrap(),
None
);
assert_eq!(
bindings.remove_axis("test_axis"),
Some(Axis::Emulated {
pos: Button::Key(VirtualKeyCode::Left),
neg: Button::Key(VirtualKeyCode::Right),
})
);
assert_eq!(
bindings
.insert_axis(
String::from("test_controller_axis"),
Axis::Controller {
controller_id: 0,
axis: ControllerAxis::RightX,
invert: false,
dead_zone: 0.25,
},
)
.unwrap(),
None
);
assert_eq!(
bindings.remove_axis("test_controller_axis"),
Some(Axis::Controller {
controller_id: 0,
axis: ControllerAxis::RightX,
invert: false,
dead_zone: 0.25,
})
);
assert_eq!(
bindings
.insert_axis(
String::from("test_mouse_wheel_axis"),
Axis::MouseWheel { horizontal: false },
)
.unwrap(),
None
);
assert_eq!(
bindings.remove_axis("test_mouse_wheel_axis"),
Some(Axis::MouseWheel { horizontal: false })
);
}
}