crankit-input 0.4.0

An ergonomic input API for the playdate
Documentation
use core::ptr;

use playdate_sys_v02::ffi::{PDButtons, PlaydateAPI};

use crate::{Button, ButtonSet, ButtonsState, ButtonsStateSource, CrankStateSource};

impl ButtonsStateSource for PlaydateAPI {
    fn buttons_state(&self) -> ButtonsState {
        unsafe { self.system.as_ref().unwrap().buttons_state() }
    }
}

impl CrankStateSource for PlaydateAPI {
    fn crank_angle_deg(&self) -> f32 {
        unsafe { self.system.as_ref().unwrap().crank_angle_deg() }
    }

    fn crank_change_deg(&self) -> f32 {
        unsafe { self.system.as_ref().unwrap().crank_change_deg() }
    }

    fn is_crank_docked(&self) -> bool {
        unsafe { self.system.as_ref().unwrap().is_crank_docked() }
    }
}

impl ButtonsStateSource for playdate_sys_v02::ffi::playdate_sys {
    fn buttons_state(&self) -> ButtonsState {
        let mut current = PDButtons(0);
        let mut pushed = PDButtons(0);
        let mut released = PDButtons(0);
        unsafe {
            self.getButtonState.unwrap()(
                ptr::addr_of_mut!(current),
                ptr::addr_of_mut!(pushed),
                ptr::addr_of_mut!(released),
            );
        }
        ButtonsState {
            current: current.into(),
            pushed: pushed.into(),
            released: released.into(),
        }
    }
}

impl CrankStateSource for playdate_sys_v02::ffi::playdate_sys {
    fn crank_angle_deg(&self) -> f32 {
        unsafe { self.getCrankAngle.unwrap()() }
    }

    fn crank_change_deg(&self) -> f32 {
        unsafe { self.getCrankChange.unwrap()() }
    }

    fn is_crank_docked(&self) -> bool {
        unsafe { self.isCrankDocked.unwrap()() == 1 }
    }
}

impl From<PDButtons> for ButtonSet {
    #[allow(clippy::cast_possible_truncation)]
    fn from(PDButtons(bits): PDButtons) -> Self {
        Self(bits as u8)
    }
}

impl From<Button> for PDButtons {
    fn from(value: Button) -> Self {
        Self(value as _)
    }
}

impl From<ButtonSet> for PDButtons {
    #[allow(clippy::cast_lossless)]
    fn from(value: ButtonSet) -> Self {
        Self(value.0 as _)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::rstest;

    #[rstest]
    #[case(PDButtons::kButtonA, Button::A, true)]
    #[case(PDButtons::kButtonA, Button::B, false)]
    #[case(PDButtons::kButtonB, Button::A, false)]
    #[case(PDButtons::kButtonB, Button::B, true)]
    #[case(PDButtons::kButtonA | PDButtons::kButtonB, Button::B, true)]
    #[case(PDButtons::kButtonA | PDButtons::kButtonB, Button::A, true)]
    #[case(PDButtons::kButtonA | PDButtons::kButtonB, Button::Up, false)]
    #[case(PDButtons::kButtonA | PDButtons::kButtonB | PDButtons::kButtonUp, Button::Up, true)]
    fn test_set_contains(
        #[case] raw_set: PDButtons,
        #[case] button: Button,
        #[case] expected: bool,
    ) {
        let set: ButtonSet = raw_set.into();
        assert_eq!(set.contains(button), expected);
        assert_eq!(set.contains_any(button), expected);
    }
}