cranpose_services/
haptics.rs1use cranpose_core::compositionLocalOfWithPolicy;
8use cranpose_core::CompositionLocal;
9use cranpose_core::CompositionLocalProvider;
10use cranpose_macros::composable;
11use std::cell::RefCell;
12use std::rc::Rc;
13
14#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16pub enum HapticFeedback {
17 ImpactLight,
19 ImpactMedium,
21 ImpactHeavy,
23 Selection,
25 Success,
27 Warning,
29 Error,
31}
32
33pub trait Haptics {
36 fn perform(&self, feedback: HapticFeedback);
37}
38
39pub type HapticsRef = Rc<dyn Haptics>;
40
41struct NoopHaptics;
42
43impl Haptics for NoopHaptics {
44 fn perform(&self, _feedback: HapticFeedback) {}
45}
46
47thread_local! {
48 static PLATFORM_HAPTICS: RefCell<Option<HapticsRef>> = const { RefCell::new(None) };
49}
50
51pub fn set_platform_haptics(haptics: HapticsRef) {
53 PLATFORM_HAPTICS.with(|cell| *cell.borrow_mut() = Some(haptics));
54}
55
56pub fn clear_platform_haptics() {
58 PLATFORM_HAPTICS.with(|cell| *cell.borrow_mut() = None);
59}
60
61pub fn default_haptics() -> HapticsRef {
62 PLATFORM_HAPTICS
63 .with(|cell| cell.borrow().clone())
64 .unwrap_or_else(|| Rc::new(NoopHaptics))
65}
66
67pub fn local_haptics() -> CompositionLocal<HapticsRef> {
68 thread_local! {
69 static LOCAL_HAPTICS: RefCell<Option<CompositionLocal<HapticsRef>>> = const { RefCell::new(None) };
70 }
71
72 LOCAL_HAPTICS.with(|cell| {
73 let mut local = cell.borrow_mut();
74 local
75 .get_or_insert_with(|| compositionLocalOfWithPolicy(default_haptics, Rc::ptr_eq))
76 .clone()
77 })
78}
79
80#[allow(non_snake_case)]
81#[composable]
82pub fn ProvideHaptics(content: impl FnOnce()) {
83 let haptics = cranpose_core::remember(default_haptics).with(|state| state.clone());
84 let local = local_haptics();
85 CompositionLocalProvider(vec![local.provides(haptics)], move || {
86 content();
87 });
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use std::cell::Cell;
94
95 #[test]
96 fn registered_haptics_receives_events() {
97 clear_platform_haptics();
98 default_haptics().perform(HapticFeedback::Selection); struct Rec(Rc<Cell<u32>>);
101 impl Haptics for Rec {
102 fn perform(&self, _f: HapticFeedback) {
103 self.0.set(self.0.get() + 1);
104 }
105 }
106 let count = Rc::new(Cell::new(0));
107 set_platform_haptics(Rc::new(Rec(count.clone())));
108 default_haptics().perform(HapticFeedback::ImpactMedium);
109 assert_eq!(count.get(), 1);
110 clear_platform_haptics();
111 }
112}