use std::num::NonZeroU16;
use openlogi_hidpp_derive::Feature;
use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PointerScaling(NonZeroU16);
impl PointerScaling {
pub const ONE: Self = match NonZeroU16::new(0x0100) {
Some(raw) => Self(raw),
None => panic!("0x0100 is non-zero"),
};
#[must_use]
pub const fn from_raw(raw: u16) -> Option<Self> {
match NonZeroU16::new(raw) {
Some(raw) => Some(Self(raw)),
None => None,
}
}
#[must_use]
pub const fn raw(self) -> u16 {
self.0.get()
}
#[must_use]
pub fn multiplier(self) -> f32 {
f32::from(self.raw()) / 256.0
}
fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
Self::from_raw(u16::from_be_bytes([payload[0], payload[1]]))
.ok_or(Hidpp20Error::UnsupportedResponse)
}
}
#[derive(Clone, Feature)]
#[creatable(id = 0x2205, version = 0)]
pub struct PointerMotionScalingFeature {
endpoint: FeatureEndpoint,
}
impl PointerMotionScalingFeature {
pub async fn get_pointer_scaling(&self) -> Result<PointerScaling, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
PointerScaling::from_payload(&payload)
}
pub async fn set_pointer_scaling(
&self,
scaling: PointerScaling,
) -> Result<PointerScaling, Hidpp20Error> {
let [msb, lsb] = scaling.raw().to_be_bytes();
let payload = self
.endpoint
.call(1, [msb, lsb, 0x00])
.await?
.extend_payload();
PointerScaling::from_payload(&payload)
}
}
#[cfg(test)]
mod tests {
use std::assert_matches;
use super::PointerScaling;
use crate::protocol::v20::Hidpp20Error;
fn payload(msb: u8, lsb: u8) -> [u8; 16] {
let mut payload = [0; 16];
payload[0] = msb;
payload[1] = lsb;
payload
}
#[test]
fn parses_scaling_most_significant_byte_first() {
let scaling = PointerScaling::from_payload(&payload(0x01, 0x80)).unwrap();
assert_eq!(scaling.raw(), 0x0180);
assert!((scaling.multiplier() - 1.5).abs() < f32::EPSILON);
}
#[test]
fn rejects_a_zero_scaling() {
assert_matches!(
PointerScaling::from_payload(&payload(0x00, 0x00)),
Err(Hidpp20Error::UnsupportedResponse)
);
assert_eq!(PointerScaling::from_raw(0), None);
}
#[test]
fn one_is_the_unscaled_default() {
assert_eq!(PointerScaling::ONE.raw(), 0x0100);
assert!((PointerScaling::ONE.multiplier() - 1.0).abs() < f32::EPSILON);
}
}