1use std::sync::{Arc, PoisonError, RwLock};
7
8pub type ModalHook = Arc<dyn Fn(bool) + Send + Sync>;
11
12static HOOK: RwLock<Option<ModalHook>> = RwLock::new(None);
13
14pub fn set_hook(hook: Option<ModalHook>) {
16 *HOOK.write().unwrap_or_else(PoisonError::into_inner) = hook;
17}
18
19pub fn clear_if(hook: &ModalHook) {
21 let mut slot = HOOK.write().unwrap_or_else(PoisonError::into_inner);
22 if slot.as_ref().is_some_and(|h| Arc::ptr_eq(h, hook)) {
23 *slot = None;
24 }
25}
26
27pub fn around<T>(f: impl FnOnce() -> T) -> T {
29 let hook = HOOK.read().unwrap_or_else(PoisonError::into_inner).clone();
30 let _end = Ended(hook.clone());
31 if let Some(h) = &hook {
32 h(true);
33 }
34 f()
35}
36
37struct Ended(Option<ModalHook>);
39
40impl Drop for Ended {
41 fn drop(&mut self) {
42 if let Some(h) = &self.0 {
43 h(false);
44 }
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use std::sync::Mutex;
51
52 use super::*;
53
54 #[test]
55 fn the_hook_brackets_the_call_even_when_it_panics() {
56 let seen: Arc<Mutex<Vec<bool>>> = Arc::default();
57 let sink = seen.clone();
58 set_hook(Some(Arc::new(move |on| sink.lock().unwrap().push(on))));
59 assert_eq!(around(|| 7), 7);
60 let r = std::panic::catch_unwind(|| around(|| -> u8 { panic!("inside the dialog") }));
61 assert!(r.is_err());
62 set_hook(None);
63 assert_eq!(around(|| 1), 1);
64 assert_eq!(*seen.lock().unwrap(), [true, false, true, false]);
65 let a: ModalHook = Arc::new(|_| {});
67 let b: ModalHook = Arc::new(|_| {});
68 set_hook(Some(a.clone()));
69 clear_if(&b);
70 assert!(HOOK.read().unwrap().is_some());
71 clear_if(&a);
72 assert!(HOOK.read().unwrap().is_none());
73 }
74}