Skip to main content

fission_core/
platform_haptics.rs

1//! Haptics host capabilities.
2
3use crate::capability::{CapabilityType, OperationCapability};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
7pub enum HapticImpactStyle {
8    Light,
9    #[default]
10    Medium,
11    Heavy,
12    Soft,
13    Rigid,
14}
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub enum HapticNotificationKind {
18    #[default]
19    Success,
20    Warning,
21    Error,
22}
23
24#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
25pub struct HapticImpactRequest {
26    pub style: HapticImpactStyle,
27}
28
29#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
30pub struct HapticNotificationRequest {
31    pub kind: HapticNotificationKind,
32}
33
34#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
35pub struct HapticPatternStep {
36    pub duration_ms: u64,
37    pub intensity: u8,
38}
39
40#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct HapticPatternRequest {
42    pub steps: Vec<HapticPatternStep>,
43}
44
45#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
46pub struct HapticError {
47    pub code: String,
48    pub message: String,
49}
50
51impl HapticError {
52    /// Creates a portable haptic error payload.
53    ///
54    /// `code` should be a stable, machine-readable reason such as
55    /// `unsupported`, `permission_denied`, or `timeout`. `message` should be a
56    /// concise human-readable explanation suitable for logs or developer-facing
57    /// diagnostics.
58    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
59        Self {
60            code: code.into(),
61            message: message.into(),
62        }
63    }
64
65    /// Creates the standard unsupported-operation error for this capability.
66    ///
67    /// `operation` should name the attempted haptic operation. Use this
68    /// from hosts that implement the capability contract but cannot provide this
69    /// operation on the current platform or hardware.
70    pub fn unsupported(operation: impl Into<String>) -> Self {
71        Self::new(
72            "unsupported",
73            format!(
74                "haptic operation `{}` is not supported by this host",
75                operation.into()
76            ),
77        )
78    }
79}
80
81pub struct HapticImpactCapability;
82impl OperationCapability for HapticImpactCapability {
83    type Request = HapticImpactRequest;
84    type Ok = ();
85    type Err = HapticError;
86}
87
88pub struct HapticNotificationCapability;
89impl OperationCapability for HapticNotificationCapability {
90    type Request = HapticNotificationRequest;
91    type Ok = ();
92    type Err = HapticError;
93}
94
95pub struct HapticSelectionCapability;
96impl OperationCapability for HapticSelectionCapability {
97    type Request = ();
98    type Ok = ();
99    type Err = HapticError;
100}
101
102pub struct HapticPatternCapability;
103impl OperationCapability for HapticPatternCapability {
104    type Request = HapticPatternRequest;
105    type Ok = ();
106    type Err = HapticError;
107}
108
109pub const HAPTIC_IMPACT: CapabilityType<HapticImpactCapability> =
110    CapabilityType::new("fission.haptics.impact");
111pub const HAPTIC_NOTIFICATION: CapabilityType<HapticNotificationCapability> =
112    CapabilityType::new("fission.haptics.notification");
113pub const HAPTIC_SELECTION: CapabilityType<HapticSelectionCapability> =
114    CapabilityType::new("fission.haptics.selection");
115pub const HAPTIC_PATTERN: CapabilityType<HapticPatternCapability> =
116    CapabilityType::new("fission.haptics.pattern");
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn haptic_requests_round_trip() {
124        let request = HapticPatternRequest {
125            steps: vec![
126                HapticPatternStep {
127                    duration_ms: 20,
128                    intensity: 128,
129                },
130                HapticPatternStep {
131                    duration_ms: 40,
132                    intensity: 255,
133                },
134            ],
135        };
136        let bytes = serde_json::to_vec(&request).unwrap();
137        let decoded: HapticPatternRequest = serde_json::from_slice(&bytes).unwrap();
138        assert_eq!(decoded, request);
139    }
140}