pub mod output;
pub mod overlay;
pub mod ps;
pub use output::{Bus, Feedback, Trigger};
pub use overlay::HidPlugin;
use glam::Vec2;
use crate::ecs::Resource;
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Finger {
pub id: u8,
pub at: Vec2,
}
#[derive(Resource, Clone, Copy, Default, Debug)]
pub struct GamepadState {
pub connected: bool,
pub model: Option<ps::Model>,
pub state: ps::State,
}
impl GamepadState {
pub fn held(&self, mask: u32) -> bool {
self.connected && self.state.held(mask)
}
pub fn fingers(&self) -> [Option<Finger>; 2] {
let Some(model) = self.model.filter(|_| self.connected) else {
return [None, None];
};
let size = model.touch_resolution();
self.state.touch.map(|touch| {
touch.map(|touch| Finger {
id: touch.id,
at: Vec2::new(touch.x as f32, touch.y as f32) / size,
})
})
}
pub fn finger(&self) -> Option<Finger> {
let [first, second] = self.fingers();
first.or(second)
}
pub fn name(&self) -> &'static str {
match self.model {
Some(model) if self.connected => model.name(),
_ => "NOT CONNECTED",
}
}
}
#[derive(Resource, Clone, Default, Debug)]
pub struct Gamepads(pub Vec<GamepadState>);
impl Gamepads {
pub fn player(&self, index: usize) -> GamepadState {
self.0.get(index).copied().unwrap_or_default()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn connected() -> GamepadState {
GamepadState {
connected: true,
model: Some(ps::Model::DualSense),
state: ps::State::default(),
}
}
#[test]
fn a_seat_with_no_pad_reads_as_disconnected() {
let pads = Gamepads(vec![connected()]);
assert!(pads.player(0).connected, "the one pad there is");
assert!(!pads.player(1).connected, "and nothing in the second seat");
assert!(!pads.player(9).connected, "or any seat past the end");
}
#[test]
fn a_pad_plugged_in_later_fills_the_next_seat() {
let mut pads = Gamepads(vec![connected()]);
assert_eq!(pads.len(), 1);
assert!(!pads.player(1).connected);
pads.0.push(connected());
assert!(
pads.player(1).connected,
"the second seat should be live without the scene doing anything",
);
}
#[test]
fn no_pads_at_all_is_not_a_special_case() {
let pads = Gamepads::default();
assert!(pads.is_empty());
assert!(!pads.player(0).connected);
assert_eq!(pads.player(0).name(), "NOT CONNECTED");
}
}