codecraft 0.2.0

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, a yakui-drawn UI, audio and gamepad haptics; its binary maps any folder, and the symbols of its Rust files, as a 3D wall of boxes
Documentation
//! Game controllers as ECS resources, plus an on-screen readout.
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;

/// A finger on the touchpad; `at` runs 0 to 1 across and down the pad.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Finger {
    pub id: u8,
    pub at: Vec2,
}

/// What the pad was sending as of this frame; present even with no pad attached.
#[derive(Resource, Clone, Copy, Default, Debug)]
pub struct GamepadState {
    pub connected: bool,
    pub model: Option<ps::Model>,
    pub state: ps::State,
}

impl GamepadState {
    /// Whether a button is held; always false with no pad attached.
    pub fn held(&self, mask: u32) -> bool {
        self.connected && self.state.held(mask)
    }

    /// Both touchpad slots as fractions of the pad; all `None` with no pad or nothing touching.
    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,
            })
        })
    }

    /// The first occupied touch slot, for a one-finger drag.
    pub fn finger(&self) -> Option<Finger> {
        let [first, second] = self.fingers();
        first.or(second)
    }

    /// The pad's name, or why there isn't one.
    pub fn name(&self) -> &'static str {
        match self.model {
            Some(model) if self.connected => model.name(),
            _ => "NOT CONNECTED",
        }
    }
}

/// Every controller plugged in this frame, in the order found; republished each frame.
#[derive(Resource, Clone, Default, Debug)]
pub struct Gamepads(pub Vec<GamepadState>);

impl Gamepads {
    /// The pad a given player holds, or a disconnected one if they have none.
    pub fn player(&self, index: usize) -> GamepadState {
        self.0.get(index).copied().unwrap_or_default()
    }

    /// How many are plugged in.
    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");
    }
}