ncurseswwin/menu/
callbacks.rs1use std::{collections::HashMap, sync::Mutex};
24use strum::IntoEnumIterator;
25use strum_macros::EnumIter;
26use ncursesw::{SCREEN, menu::MENU};
27use crate::menu::Menu;
28
29static MODULE_PATH: &str = "ncurseswwin::menu::callbacks::";
30
31#[derive(PartialEq, Eq, Hash)]
32struct MenuKey {
33 menu: MENU
34}
35
36impl MenuKey {
37 fn new(menu: MENU) -> Self {
38 Self { menu }
39 }
40}
41
42unsafe impl Send for MenuKey { }
43unsafe impl Sync for MenuKey { }
44
45#[derive(Copy, Clone, PartialEq, Eq, Hash)]
46struct MenuValue {
47 screen: Option<SCREEN>
48}
49
50impl MenuValue {
51 fn new(screen: Option<SCREEN>) -> Self {
52 Self { screen }
53 }
54
55 fn screen(&self) -> Option<SCREEN> {
56 self.screen
57 }
58}
59
60unsafe impl Send for MenuValue { }
61unsafe impl Sync for MenuValue { }
62
63#[derive(Clone, Copy, EnumIter, Debug, PartialEq, Eq, Hash)]
64pub(in crate::menu) enum CallbackType {
65 ItemInit,
66 ItemTerm,
67 MenuInit,
68 MenuTerm
69}
70
71#[derive(PartialEq, Eq, Hash)]
72struct CallbackKey {
73 menu: Option<MENU>,
74 callback_type: CallbackType
75}
76
77impl CallbackKey {
78 fn new(menu: Option<MENU>, callback_type: CallbackType) -> Self {
79 Self { menu, callback_type }
80 }
81}
82
83unsafe impl Send for CallbackKey { }
84unsafe impl Sync for CallbackKey { }
85
86type Callback = Option<Box<dyn Fn(&Menu) + Send>>;
87
88lazy_static! {
89 static ref MENUSCREENS: Mutex<HashMap<MenuKey, MenuValue>> = Mutex::new(HashMap::new());
90 static ref CALLBACKS: Mutex<HashMap<CallbackKey, Callback>> = Mutex::new(HashMap::new());
91}
92
93macro_rules! extern_menu_callback {
94 ($func: ident, $cb_t: ident) => {
95 pub(in crate::menu) extern fn $func(menu: MENU) {
96 menu_callback(menu, CallbackType::$cb_t)
97 }
98 }
99}
100
101extern_menu_callback!(extern_item_init, ItemInit);
102extern_menu_callback!(extern_item_term, ItemTerm);
103extern_menu_callback!(extern_menu_init, MenuInit);
104extern_menu_callback!(extern_menu_term, MenuTerm);
105
106fn menu_callback(menu: MENU, cb_type: CallbackType) {
107 let get_menu = || -> Menu {
108 let screen = MENUSCREENS
109 .lock()
110 .unwrap_or_else(|_| panic!("{}menu_callback({:p}, {:?}) : MENUSCREENS.lock() failed!!!", MODULE_PATH, menu, cb_type))
111 .get(&MenuKey::new(menu))
112 .unwrap_or_else(|| panic!("{}menu_callback({:p}, {:?}) : MENUSCREENS.lock().get() failed!!!", MODULE_PATH, menu, cb_type))
113 .screen();
114
115 Menu::_from(screen, menu, unsafe { (*menu).items }, false)
116 };
117
118 let callbacks = CALLBACKS
119 .lock()
120 .unwrap_or_else(|_| panic!("{}menu_callback({:p}, {:?}) : CALLBACKS.lock() failed!!!", MODULE_PATH, menu, cb_type));
121
122 if let Some(ref callback) = callbacks
123 .get(&CallbackKey::new(Some(menu), cb_type))
124 .unwrap_or(&None)
125 {
126 callback(&get_menu())
127 } else if let Some(ref callback) = callbacks
128 .get(&CallbackKey::new(None, cb_type))
129 .unwrap_or(&None)
130 {
131 callback(&get_menu())
132 } else {
133 panic!("{}menu_callback({:p}, {:?}) : callbacks.lock().get() returned None!!!", MODULE_PATH, menu, cb_type)
134 }
135}
136
137pub(in crate::menu) fn set_menu_screen(menu: MENU, screen: Option<SCREEN>) {
138 MENUSCREENS
139 .lock()
140 .unwrap_or_else(|_| panic!("{}set_menu_screen({:p}) : MENUSCREENS.lock() failed!!!", MODULE_PATH, menu))
141 .insert(MenuKey::new(menu), MenuValue::new(screen));
142}
143
144pub(in crate::menu) fn set_menu_callback<F>(menu: Option<MENU>, cb_type: CallbackType, func: F)
145 where F: Fn(&Menu) + 'static + Send
146{
147 CALLBACKS
148 .lock()
149 .unwrap_or_else(|_| panic!("{}set_menu_callback() : CALLBACKS.lock() failed!!!", MODULE_PATH))
150 .insert(CallbackKey::new(menu, cb_type), Some(Box::new(move |menu| func(menu))));
151}
152
153pub(in crate::menu) fn menu_tidyup(menu: MENU) {
154 let mut menu_screens = MENUSCREENS
155 .lock()
156 .unwrap_or_else(|_| panic!("{}menu_tidyup({:p}) : MENUSCREENS.lock() failed!!!", MODULE_PATH, menu));
157
158 menu_screens.remove(&MenuKey::new(menu));
159 menu_screens.shrink_to_fit();
160
161 let mut callbacks = CALLBACKS
162 .lock()
163 .unwrap_or_else(|_| panic!("{}menu_tidyup({:p}) : CALLBACKS.lock() failed!!!", MODULE_PATH, menu));
164
165 let mut shrink_to_fit = false;
166
167 for cb_type in CallbackType::iter() {
168 if callbacks.remove(&CallbackKey::new(Some(menu), cb_type)).is_some() {
169 shrink_to_fit = true;
170 }
171 }
172
173 if shrink_to_fit {
174 callbacks.shrink_to_fit();
175 }
176}