1use core::ops::Range;
2use std::collections::BTreeMap;
3
4#[derive(Default)]
5pub struct QuickSwitchMenu {
6 tray_virtual_desktops_quick: Vec<(nwg::MenuItem, u32)>,
8
9 tray_separators: Vec<nwg::MenuSeparator>,
10
11 tray_submenus: Vec<(nwg::Menu, u32)>,
15
16 pub shortcuts: BTreeMap<String, u32>,
20 pub shortcuts_only_in_root: bool,
22
23 call_queue: Vec<(nwg::ControlHandle, u32, u32, u32)>,
31 is_recursive_call: bool,
34}
35impl QuickSwitchMenu {
36 pub fn get_clicked_desktop_index(&self, handle: nwg::ControlHandle) -> Option<usize> {
39 if !matches!(handle, nwg::ControlHandle::MenuItem { .. }) {
40 return None;
41 }
42 self.tray_virtual_desktops_quick
43 .iter()
44 .find(|(d, _)| d.handle == handle)
45 .map(|(_, ix)| *ix as usize)
46 }
47 pub fn get_desktop_index_so_far(&self, submenu_handle: isize) -> Option<usize> {
50 self.tray_submenus
51 .iter()
52 .find(|(d, _)| matches!(d.handle.hmenu(), Some((_, handle)) if handle as isize == submenu_handle))
53 .map(|(_, ix)| *ix as usize)
54 }
55 pub fn first_item_in_submenu(&self, submenu_handle: isize) -> Option<nwg::ControlHandle> {
57 self.tray_submenus
59 .iter()
60 .find_map(|(d, _)| {
61 let (parent, _) = d.handle.hmenu()?;
62 (parent as isize == submenu_handle).then_some(d.handle)
63 })
64 .or_else(|| {
65 self.tray_virtual_desktops_quick
66 .iter()
67 .find_map(|(item, _)| {
68 let (parent, _) = item.handle.hmenu_item()?;
69 (parent as isize == submenu_handle).then_some(item.handle)
70 })
71 })
72 }
73
74 pub fn clear(&mut self) {
75 self.tray_virtual_desktops_quick.clear();
76 self.tray_separators.clear();
77 self.tray_submenus
78 .drain(..)
79 .rev() .for_each(|(menu, _)| crate::nwg_ext::menu_remove(&menu));
81 }
82
83 #[allow(dead_code)]
84 pub fn has_submenu(&self, menu_handle: isize) -> bool {
85 self.tray_submenus.iter().any(|(menu, _)| {
86 menu.handle
87 .hmenu()
88 .map_or(false, |(_, id)| id as isize == menu_handle)
89 })
90 }
91
92 fn create_shortcut_items(&mut self, parent: nwg::ControlHandle) {
96 let mut separator = Default::default();
98 nwg::MenuSeparator::builder()
99 .parent(parent)
100 .build(&mut separator)
101 .expect("Failed to build separator for quick switch menu's shortcut section");
102 self.tray_separators.push(separator);
103
104 for (shortcut, &virtual_desktop_ix) in &self.shortcuts {
105 let mut item = Default::default();
106 nwg::MenuItem::builder()
107 .text(&format!(
108 "Virtual desktop {}\t&{shortcut}",
109 virtual_desktop_ix.saturating_add(1)
110 ))
111 .parent(parent)
112 .build(&mut item)
113 .expect("Failed to build \"shortcut\" menu item for quick switch menu");
114 self.tray_virtual_desktops_quick
115 .push((item, virtual_desktop_ix));
116 }
117 }
118 fn try_create_leaf_items(&mut self, parent: nwg::ControlHandle, range: Range<u32>) -> bool {
122 if range.len() <= 10 {
123 let mut last_digit = [0_u32; 10];
125 for ix in range.clone() {
126 last_digit[(ix % 10) as usize] += 1;
127 }
128
129 if last_digit.iter().all(|&x| x <= 1) {
130 for ix in range {
132 let tens = ix / 10;
133 let ones = ix % 10;
134 let mut item = Default::default();
135 nwg::MenuItem::builder()
136 .text(&format!(
137 "Virtual desktop {}&{ones}",
138 if tens == 0 {
139 String::new()
140 } else {
141 tens.to_string()
142 }
143 ))
144 .parent(parent)
145 .build(&mut item)
146 .expect("Failed to build \"leaf\" menu item for quick switch menu");
147
148 self.tray_virtual_desktops_quick.push((item, ix - 1));
151 }
152 return true;
153 }
154 }
155 false
156 }
157 fn create_submenus(
163 &mut self,
164 parent: nwg::ControlHandle,
165 desktop_count: u32,
166 prefix: u32,
167 remaining_digits: u32,
168 start_digit: u32,
169 ) -> usize {
170 if remaining_digits == 0 {
171 return 0;
172 }
173 let unit = 10_u32.pow(remaining_digits - 1);
175 let start = ((prefix * 10 + start_digit) * unit).max(1);
176 let end = ((prefix + 1) * unit * 10).min(desktop_count);
177 let end_digit = (end - 1) / unit % 10;
178 let max_digits = if prefix == 0 { 0 } else { prefix.ilog10() + 1 } + remaining_digits;
179
180 let get_range_for = |digit: u32| {
181 let start = if digit == start_digit {
182 start
183 } else {
184 (prefix * 10 * unit) + digit * unit
186 };
187 let end = if digit == end_digit {
188 end
189 } else {
190 (prefix * 10 * unit) + (digit + 1) * unit
192 };
193 (start, end)
194 };
195 for digit in start_digit..end_digit + 1 {
196 let (start, end) = get_range_for(digit);
197 let mut menu = Default::default();
198 let start_prefix = start / (unit * 10);
199 let start_suffix = start % (unit * 10);
200
201 let prefix_width = max_digits.saturating_sub(remaining_digits) as usize;
202 nwg::Menu::builder()
203 .text(&format!(
204 "{}&{start_suffix:0suffix_width$} - {:0width$}",
205 if prefix_width == 0 {
206 debug_assert_eq!(start_prefix, 0);
207 String::new()
208 } else {
209 format!("{start_prefix:0prefix_width$}")
210 },
211 end - 1,
212 suffix_width = remaining_digits as usize,
213 width = max_digits as usize
214 ))
215 .parent(parent)
216 .build(&mut menu)
217 .expect("Failed to build submenu for quick switch menu");
218
219 let handle = menu.handle;
220 self.tray_submenus.push((
224 menu,
225 (start_prefix * 10 + start_suffix % 10)
226 .saturating_sub(1),
228 ));
229 self.call_queue.push((
230 handle,
231 desktop_count,
232 prefix * 10 + digit,
233 remaining_digits - 1,
234 ));
235 }
236 (start_digit..end_digit + 1).len()
237 }
238 pub fn create_quick_switch_menu(&mut self, parent: nwg::ControlHandle, desktop_count: u32) {
256 self._create_quick_switch_menu(parent, desktop_count, 0, desktop_count.ilog10() + 1);
257 }
258 fn _create_quick_switch_menu(
259 &mut self,
260 parent: nwg::ControlHandle,
261 desktop_count: u32,
262 prefix: u32,
263 remaining_digits: u32,
264 ) {
265 let is_root = !self.is_recursive_call;
266 if remaining_digits == 0 {
267 return;
268 }
269 if remaining_digits == 1 {
270 let start = (prefix * 10).max(1);
271 let end = ((prefix + 1) * 10).min(desktop_count);
272 let _did_create = self.try_create_leaf_items(parent, start..end);
273 debug_assert!(
274 _did_create,
275 "Should have created leaf items, otherwise our range was incorrect, used the range: {:?}",
276 start..end
277 );
278 if !self.shortcuts_only_in_root || is_root {
279 self.create_shortcut_items(parent);
280 }
281 return;
282 }
283
284 struct Guard<'a> {
286 this: &'a mut QuickSwitchMenu,
287 is_recursive_call: bool,
289 }
290 impl<'a> Guard<'a> {
291 fn new(this: &'a mut QuickSwitchMenu) -> Self {
292 let is_recursive_call = this.is_recursive_call;
293 this.is_recursive_call = true;
294 Self {
295 this,
296 is_recursive_call,
297 }
298 }
299 }
300 impl Drop for Guard<'_> {
301 fn drop(&mut self) {
302 if !self.is_recursive_call {
303 while let Some((parent, desktop_count, prefix, remaining_digits)) =
304 self.this.call_queue.pop()
305 {
306 self.this._create_quick_switch_menu(
307 parent,
308 desktop_count,
309 prefix,
310 remaining_digits,
311 );
312 }
313 }
314 self.this.is_recursive_call = self.is_recursive_call;
316 }
317 }
318 let guard = Guard::new(self);
319 let this = &mut *guard.this;
320
321 let created_menus =
322 this.create_submenus(parent, desktop_count, prefix, remaining_digits, 0);
323 if created_menus >= 10 && prefix != 0 {
324 return;
326 }
327
328 let mut separator = Default::default();
332 nwg::MenuSeparator::builder()
333 .parent(parent)
334 .build(&mut separator)
335 .expect("Failed to build separator for quick switch menu");
336 this.tray_separators.push(separator);
337
338 if remaining_digits == 2 {
339 let start = (prefix * 100 + created_menus as u32).max(1);
340 let end = ((prefix * 10 + 1) * 10).min(desktop_count);
341 let _did_create = this.try_create_leaf_items(parent, start..end);
342 debug_assert!(
343 _did_create,
344 "Should have created trailing leaf items, otherwise our range was incorrect, used the range: {:?}",
345 start..end
346 );
347 } else {
348 this.create_submenus(
349 parent,
350 desktop_count,
351 prefix * 10,
352 remaining_digits - 1,
353 created_menus as u32,
354 );
355 }
356
357 if !this.shortcuts_only_in_root || is_root {
358 this.create_shortcut_items(parent);
359 }
360
361 drop(guard);
364 }
365}
366impl Drop for QuickSwitchMenu {
367 fn drop(&mut self) {
368 self.clear();
369 }
370}