Skip to main content

fission_shell_winit/
haptics.rs

1use fission_core::{
2    HapticError, HapticImpactRequest, HapticNotificationRequest, HapticPatternRequest,
3    HAPTIC_IMPACT, HAPTIC_NOTIFICATION, HAPTIC_PATTERN, HAPTIC_SELECTION,
4};
5use fission_shell::async_host::AsyncRegistry;
6use std::sync::{Arc, Mutex};
7
8/// Host-side haptic feedback provider used by shell capability registration.
9pub trait HapticHost: Send + Sync + 'static {
10    /// Plays impact feedback with the requested strength.
11    fn impact(&self, request: HapticImpactRequest) -> Result<(), HapticError>;
12    /// Plays success, warning, or error notification feedback.
13    fn notification(&self, request: HapticNotificationRequest) -> Result<(), HapticError>;
14    /// Plays lightweight selection-change feedback.
15    fn selection(&self) -> Result<(), HapticError>;
16    /// Plays a bounded custom haptic pattern.
17    fn pattern(&self, request: HapticPatternRequest) -> Result<(), HapticError>;
18}
19
20#[derive(Debug, Default)]
21pub struct UnsupportedHapticHost;
22
23impl HapticHost for UnsupportedHapticHost {
24    fn impact(&self, _request: HapticImpactRequest) -> Result<(), HapticError> {
25        Err(HapticError::unsupported("impact"))
26    }
27
28    fn notification(&self, _request: HapticNotificationRequest) -> Result<(), HapticError> {
29        Err(HapticError::unsupported("notification"))
30    }
31
32    fn selection(&self) -> Result<(), HapticError> {
33        Err(HapticError::unsupported("selection"))
34    }
35
36    fn pattern(&self, _request: HapticPatternRequest) -> Result<(), HapticError> {
37        Err(HapticError::unsupported("pattern"))
38    }
39}
40
41#[derive(Debug, Default)]
42pub struct MemoryHapticHost {
43    calls: Arc<Mutex<Vec<String>>>,
44}
45
46impl MemoryHapticHost {
47    pub fn calls(&self) -> Vec<String> {
48        self.calls
49            .lock()
50            .map(|calls| calls.clone())
51            .unwrap_or_default()
52    }
53}
54
55impl HapticHost for MemoryHapticHost {
56    fn impact(&self, _request: HapticImpactRequest) -> Result<(), HapticError> {
57        self.calls.lock().unwrap().push("impact".into());
58        Ok(())
59    }
60
61    fn notification(&self, _request: HapticNotificationRequest) -> Result<(), HapticError> {
62        self.calls.lock().unwrap().push("notification".into());
63        Ok(())
64    }
65
66    fn selection(&self) -> Result<(), HapticError> {
67        self.calls.lock().unwrap().push("selection".into());
68        Ok(())
69    }
70
71    fn pattern(&self, _request: HapticPatternRequest) -> Result<(), HapticError> {
72        self.calls.lock().unwrap().push("pattern".into());
73        Ok(())
74    }
75}
76
77pub(crate) fn register_haptic_capabilities(
78    async_registry: &mut AsyncRegistry,
79    host: Arc<dyn HapticHost>,
80) {
81    let impact_host = host.clone();
82    async_registry.register_operation_capability(HAPTIC_IMPACT, move |request, _| {
83        let host = impact_host.clone();
84        async move { host.impact(request) }
85    });
86
87    let notification_host = host.clone();
88    async_registry.register_operation_capability(HAPTIC_NOTIFICATION, move |request, _| {
89        let host = notification_host.clone();
90        async move { host.notification(request) }
91    });
92
93    let selection_host = host.clone();
94    async_registry.register_operation_capability(HAPTIC_SELECTION, move |(), _| {
95        let host = selection_host.clone();
96        async move { host.selection() }
97    });
98
99    async_registry.register_operation_capability(HAPTIC_PATTERN, move |request, _| {
100        let host = host.clone();
101        async move { host.pattern(request) }
102    });
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use fission_core::HapticImpactStyle;
109
110    #[test]
111    fn unsupported_host_reports_errors() {
112        let host = UnsupportedHapticHost;
113        assert!(host.selection().is_err());
114    }
115
116    #[test]
117    fn memory_host_records_calls() {
118        let host = MemoryHapticHost::default();
119        host.impact(HapticImpactRequest {
120            style: HapticImpactStyle::Heavy,
121        })
122        .unwrap();
123        host.selection().unwrap();
124        assert_eq!(host.calls(), vec!["impact", "selection"]);
125    }
126}