1use nwg::MenuSeparator;
2
3use crate::{
4 dynamic_gui::{forward_to_dynamic_ui, DynamicUiHooks, DynamicUiWrapper},
5 nwg_ext::menu_remove,
6 settings::{AutoStart, QuickSwitchMenu, TrayIconType, UiSettings},
7 tray::{MenuKeyPressEffect, MenuPosition, SystemTray, SystemTrayRef, TrayPlugin, TrayRoot},
8 vd,
9};
10use std::{
11 any::TypeId,
12 cell::{Cell, RefCell},
13 collections::{BTreeMap, VecDeque},
14 rc::Rc,
15 sync::Arc,
16 time::{Duration, Instant},
17};
18
19#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub enum SubMenu {
21 Handle(nwg::ControlHandle),
23 #[allow(dead_code)]
28 AccessKey(u8),
29}
30impl SubMenu {
31 fn open(self) {
32 let Some(context_menu) = crate::nwg_ext::find_context_menu_window() else {
33 tracing::warn!(wanted_sub_menu =? self, "Failed to find context menu window");
34 return;
35 };
36
37 match self {
38 SubMenu::Handle(control_handle) => {
39 let Some(index) = crate::nwg_ext::menu_index_in_parent(control_handle) else {
40 tracing::warn!("Failed to find settings submenu");
41 return;
42 };
43
44 use windows::Win32::{
45 Foundation::{LPARAM, WPARAM},
46 UI::{
47 Input::KeyboardAndMouse::VK_RETURN,
48 WindowsAndMessaging::{PostMessageW, WM_KEYDOWN},
49 },
50 };
51
52 unsafe {
53 _ = PostMessageW(
55 context_menu,
56 0x1e5,
57 WPARAM(usize::try_from(index).unwrap()),
58 LPARAM(0),
59 );
60 _ = PostMessageW(
62 context_menu,
63 WM_KEYDOWN,
64 WPARAM(usize::from(VK_RETURN.0)),
65 LPARAM(0),
66 );
67 }
68 }
69 SubMenu::AccessKey(key) => {
70 use windows::Win32::{
71 Foundation::{LPARAM, WPARAM},
72 UI::WindowsAndMessaging::{PostMessageW, WM_KEYDOWN},
73 };
74 unsafe {
75 _ = PostMessageW(
76 context_menu,
77 WM_KEYDOWN,
78 WPARAM(usize::from(key)),
79 LPARAM(0),
80 );
81 }
82 }
83 }
84 }
85}
86
87#[derive(Default, nwd::NwgPartial)]
90pub struct OpenSubmenuPlugin {
91 submenus: RefCell<VecDeque<SubMenu>>,
92 queued_at: Cell<Option<Instant>>,
93}
94impl OpenSubmenuPlugin {
95 const MAX_DELAY: Duration = Duration::from_millis(4000);
98
99 pub fn queue_open_of(&self, submenu: impl IntoIterator<Item = SubMenu>) {
100 let items = submenu.into_iter().collect::<Vec<_>>();
101 let mut guard = self.submenus.borrow_mut();
102 guard.clear();
103 guard.extend(items);
104 self.queued_at.set(Some(Instant::now()));
105 }
106}
107impl DynamicUiHooks<SystemTray> for OpenSubmenuPlugin {
108 fn before_partial_build(
109 &mut self,
110 tray_ui: &Rc<SystemTray>,
111 _should_build: &mut bool,
112 ) -> Option<(nwg::ControlHandle, TypeId)> {
113 Some((tray_ui.root().tray_menu.handle, TypeId::of::<TrayRoot>()))
114 }
115 fn after_process_events(
116 &self,
117 _dynamic_ui: &Rc<SystemTray>,
118 evt: nwg::Event,
119 _evt_data: &nwg::EventData,
120 _handle: nwg::ControlHandle,
121 _window: nwg::ControlHandle,
122 ) {
123 if let nwg::Event::OnMenuOpen = evt {
124 let Some(queue_time) = self.queued_at.get() else {
125 return;
127 };
128 if queue_time.elapsed() > Self::MAX_DELAY {
129 self.submenus.borrow_mut().clear();
131 self.queued_at.set(None);
132 return;
133 }
134 let Some(next) = self.submenus.borrow_mut().pop_front() else {
135 self.queued_at.set(None);
136 return;
137 };
138 next.open();
139 }
140 }
141}
142impl TrayPlugin for OpenSubmenuPlugin {}
143
144#[derive(Default, nwd::NwgPartial)]
147pub struct QuickSwitchTopMenu {
148 #[nwg_control(text: "&Quick Switch")]
149 tray_quick_menu: nwg::Menu,
150
151 #[nwg_control()]
152 tray_sep: nwg::MenuSeparator,
153
154 is_built: bool,
155}
156impl QuickSwitchTopMenu {
157 pub fn menu_handle(&self) -> Option<nwg::ControlHandle> {
159 Some(self.tray_quick_menu.handle).filter(|_| self.is_built)
160 }
161}
162impl DynamicUiHooks<SystemTray> for QuickSwitchTopMenu {
163 fn before_partial_build(
164 &mut self,
165 tray_ui: &Rc<SystemTray>,
166 should_build: &mut bool,
167 ) -> Option<(nwg::ControlHandle, TypeId)> {
168 let should_enable = tray_ui.settings().get().quick_switch_menu == QuickSwitchMenu::SubMenu;
169 if !should_enable {
170 *should_build = false;
171 return None;
172 }
173 Some((tray_ui.root().tray_menu.handle, TypeId::of::<TrayRoot>()))
174 }
175 fn after_partial_build(&mut self, _dynamic_ui: &Rc<SystemTray>) {
176 self.is_built = true;
177 }
178 fn need_rebuild(&self, tray_ui: &Rc<SystemTray>) -> bool {
179 let should_enable = tray_ui.settings().get().quick_switch_menu == QuickSwitchMenu::SubMenu;
180 should_enable != self.is_built
181 }
182 fn before_rebuild(&mut self, _dynamic_ui: &Rc<SystemTray>) {
183 menu_remove(&self.tray_quick_menu);
184 *self = Default::default();
185 }
186}
187impl TrayPlugin for QuickSwitchTopMenu {
188 fn on_current_desktop_changed(&self, _tray_ui: &Rc<SystemTray>, current_desktop_index: u32) {
189 if !self.is_built {
190 return;
191 }
192 crate::nwg_ext::menu_set_text(
193 self.tray_quick_menu.handle,
194 &format!("&Quick Switch from Desktop {}", current_desktop_index + 1),
195 );
196 }
197}
198
199#[derive(Default, nwd::NwgPartial)]
200pub struct TopMenuItems {
201 tray_ui: SystemTrayRef,
202
203 #[nwg_control(text: "&Close Current Desktop")]
204 #[nwg_events(OnMenuItemSelected: [Self::close_current_desktop])]
205 tray_close_desktop: nwg::MenuItem,
206
207 #[nwg_control(text: "&New Desktop")]
208 #[nwg_events(OnMenuItemSelected: [Self::create_desktop])]
209 tray_create_desktop: nwg::MenuItem,
210
211 #[nwg_control()]
212 tray_sep1: nwg::MenuSeparator,
213
214 #[nwg_control(text: "&Smooth Desktop Switch")]
215 #[nwg_events(OnMenuItemSelected: [Self::toggle_smooth_switch])]
216 pub tray_smooth_switch: nwg::MenuItem,
217
218 #[nwg_control(text: "More &Options")]
219 tray_settings_menu: nwg::Menu,
220
221 #[cfg(feature = "admin_startup")]
222 #[nwg_control(text: "&Request Admin at Startup", parent: tray_settings_menu)]
223 #[nwg_events(OnMenuItemSelected: [Self::toggle_request_admin_at_startup])]
224 tray_request_admin_at_startup: nwg::MenuItem,
225
226 #[nwg_control(text: "Tray &Icon", parent: tray_settings_menu)]
227 tray_icon_menu: nwg::Menu,
228
229 tray_icon_types: Vec<nwg::MenuItem>,
231
232 #[nwg_control(text: "&Quick Switch Menu", parent: tray_settings_menu)]
233 tray_quick_switch_menu: nwg::Menu,
234
235 tray_quick_switch_items: Vec<nwg::MenuItem>,
237
238 #[nwg_control(text: "Auto &Start", parent: tray_settings_menu)]
239 tray_auto_start_menu: nwg::Menu,
240
241 tray_auto_start_items: Vec<nwg::MenuItem>,
243
244 #[nwg_control()]
245 tray_sep2: nwg::MenuSeparator,
246}
247impl DynamicUiHooks<SystemTray> for TopMenuItems {
248 fn before_partial_build(
249 &mut self,
250 tray_ui: &Rc<SystemTray>,
251 _should_build: &mut bool,
252 ) -> Option<(nwg::ControlHandle, TypeId)> {
253 Some((tray_ui.root().tray_menu.handle, TypeId::of::<TrayRoot>()))
254 }
255 fn after_partial_build(&mut self, tray_ui: &Rc<SystemTray>) {
256 self.tray_ui.set(tray_ui);
257 let settings = tray_ui.settings().get();
258
259 #[cfg(feature = "admin_startup")]
260 {
261 self.tray_request_admin_at_startup
262 .set_checked(settings.request_admin_at_startup);
263 self.update_label_for_request_admin_at_startup();
264 }
265
266 self.tray_smooth_switch
267 .set_checked(settings.smooth_switch_desktops);
268 self.update_label_for_smooth_switch();
269
270 {
271 let menu_items = &mut self.tray_icon_types;
272 menu_items.clear();
273
274 for tray_icon in TrayIconType::ALL {
275 let mut item = Default::default();
276 let res = nwg::MenuItem::builder()
277 .text(&format!("{tray_icon:?}"))
278 .parent(self.tray_icon_menu.handle)
279 .build(&mut item);
280 if let Err(e) = res {
281 tracing::error!(
282 "Failed to build menu item for tray icon type {tray_icon:?}: {e}"
283 );
284 }
285 menu_items.push(item);
286 }
287 }
288 self.check_selected_tray_icon(settings.tray_icon_type);
289
290 {
291 let menu_items = &mut self.tray_quick_switch_items;
292 menu_items.clear();
293
294 for option in QuickSwitchMenu::ALL {
295 let mut item = Default::default();
296 let res = nwg::MenuItem::builder()
297 .text(&format!("{option:?}"))
298 .parent(self.tray_quick_switch_menu.handle)
299 .build(&mut item);
300 if let Err(e) = res {
301 tracing::error!(
302 "Failed to build menu item for quick switch option \"{option:?}\": {e}"
303 );
304 }
305 menu_items.push(item);
306 }
307 }
308 self.check_selected_quick_switch(settings.quick_switch_menu);
309
310 {
311 let menu_items = &mut self.tray_auto_start_items;
312 menu_items.clear();
313
314 for option in AutoStart::ALL {
315 let mut item = Default::default();
316 let res = nwg::MenuItem::builder()
317 .text(&format!("{option:?}"))
318 .parent(self.tray_auto_start_menu.handle)
319 .build(&mut item);
320 if let Err(e) = res {
321 tracing::error!(
322 "Failed to build menu item for auto start option \"{option:?}\": {e}"
323 );
324 }
325 menu_items.push(item);
326 }
327 }
328 self.check_selected_auto_start(settings.auto_start);
329 }
330 fn before_rebuild(&mut self, tray_ui: &Rc<SystemTray>) {
331 *self = Default::default();
332 self.tray_ui.set(tray_ui);
333 }
334 fn after_process_events(
335 &self,
336 dynamic_ui: &Rc<SystemTray>,
337 evt: nwg::Event,
338 _evt_data: &nwg::EventData,
339 handle: nwg::ControlHandle,
340 _window: nwg::ControlHandle,
341 ) {
342 if let nwg::Event::OnMenuItemSelected = evt {
343 let new_icon = self
344 .tray_icon_types
345 .iter()
346 .zip(TrayIconType::ALL)
347 .find(|(item, _)| item.handle == handle)
348 .map(|(_, icon)| *icon);
349
350 if let Some(new_icon) = new_icon {
351 dynamic_ui.settings().update(|prev| UiSettings {
352 tray_icon_type: new_icon,
353 ..prev.clone()
354 });
355 }
356
357 let wanted_quick_switch = self
358 .tray_quick_switch_items
359 .iter()
360 .zip(QuickSwitchMenu::ALL)
361 .find(|(item, _)| item.handle == handle)
362 .map(|(_, option)| *option);
363
364 if let Some(wanted_quick_switch) = wanted_quick_switch {
365 dynamic_ui.settings().update(|prev| UiSettings {
366 quick_switch_menu: wanted_quick_switch,
367 ..prev.clone()
368 });
369 }
370
371 let auto_start = self
372 .tray_auto_start_items
373 .iter()
374 .zip(AutoStart::ALL)
375 .find(|(item, _)| item.handle == handle)
376 .map(|(_, option)| *option);
377
378 if let Some(auto_start) = auto_start {
379 dynamic_ui.settings().update(|prev| UiSettings {
380 auto_start,
381 ..prev.clone()
382 });
383 }
384 }
385 }
386}
387impl TrayPlugin for TopMenuItems {
388 fn on_settings_changed(
389 &self,
390 _tray_ui: &Rc<SystemTray>,
391 prev: &Arc<UiSettings>,
392 settings: &Arc<UiSettings>,
393 ) {
394 #[cfg(feature = "admin_startup")]
395 {
396 if self.tray_request_admin_at_startup.checked() != settings.request_admin_at_startup {
397 self.tray_request_admin_at_startup
398 .set_checked(settings.request_admin_at_startup);
399 self.update_label_for_request_admin_at_startup();
400 }
401 }
402
403 if self.tray_smooth_switch.checked() != settings.smooth_switch_desktops {
404 self.tray_smooth_switch
405 .set_checked(settings.smooth_switch_desktops);
406 self.update_label_for_smooth_switch();
407 }
408
409 if prev.tray_icon_type != settings.tray_icon_type {
410 self.check_selected_tray_icon(settings.tray_icon_type);
411 }
412 if prev.quick_switch_menu != settings.quick_switch_menu {
413 self.check_selected_quick_switch(settings.quick_switch_menu);
414 }
415 if prev.auto_start != settings.auto_start {
416 self.check_selected_auto_start(settings.auto_start);
417 }
418 }
419}
420impl TopMenuItems {
422 fn close_current_desktop(&self) {
423 let Some(tray_ui) = self.tray_ui.get() else {
424 return;
425 };
426 let result = vd::get_current_desktop().and_then(|current| {
427 let ix = current.get_index()?;
428 vd::remove_desktop(
429 current,
430 vd::Desktop::from(ix.checked_sub(1).unwrap_or(1)),
433 )?;
434 Ok(())
435 });
436 if let Err(e) = result {
437 tray_ui.show_notification(
438 "Virtual Desktop Manager Error",
439 &format!("Failed to create a new virtual desktop with: {e:?}"),
440 );
441 }
442 }
443 fn create_desktop(&self) {
444 let Some(tray_ui) = self.tray_ui.get() else {
445 return;
446 };
447 if let Err(e) = vd::create_desktop() {
448 tray_ui.show_notification(
449 "Virtual Desktop Manager Error",
450 &format!("Failed to create a new virtual desktop with: {e:?}"),
451 );
452 }
453 }
454 fn toggle_smooth_switch(&self) {
455 let Some(tray_ui) = self.tray_ui.get() else {
456 return;
457 };
458 let new_value = !self.tray_smooth_switch.checked();
459 self.tray_smooth_switch.set_checked(new_value);
460 tray_ui.settings().update(|prev| UiSettings {
461 smooth_switch_desktops: new_value,
462 ..prev.clone()
463 });
464 self.update_label_for_smooth_switch();
465 tray_ui.show_menu(MenuPosition::AtPrevious);
466 }
467 #[cfg(feature = "admin_startup")]
468 fn toggle_request_admin_at_startup(&self) {
469 let Some(tray_ui) = self.tray_ui.get() else {
470 return;
471 };
472 let new_value = !self.tray_request_admin_at_startup.checked();
473 self.tray_request_admin_at_startup.set_checked(new_value);
474 tray_ui.settings().update(|prev| UiSettings {
475 request_admin_at_startup: new_value,
476 ..prev.clone()
477 });
478 self.update_label_for_request_admin_at_startup();
479 if let Some(plugin) = tray_ui.get_dynamic_ui().get_ui::<OpenSubmenuPlugin>() {
480 plugin.queue_open_of([SubMenu::Handle(self.tray_settings_menu.handle)]);
481 };
482 tray_ui.show_menu(MenuPosition::AtPrevious);
483 }
484}
485impl TopMenuItems {
487 #[cfg(feature = "admin_startup")]
488 fn update_label_for_request_admin_at_startup(&self) {
489 let checked = self.tray_request_admin_at_startup.checked();
490 crate::nwg_ext::menu_set_text(
491 self.tray_request_admin_at_startup.handle,
492 &format!(
493 "Request Admin at Startup ({})",
494 if checked { "On" } else { "Off" }
495 ),
496 );
497 }
498 fn update_label_for_smooth_switch(&self) {
499 let checked = self.tray_smooth_switch.checked();
500 crate::nwg_ext::menu_set_text(
501 self.tray_smooth_switch.handle,
502 &format!(
503 "&Smooth Desktop Switch ({})",
504 if checked { "On" } else { "Off" }
505 ),
506 );
507 }
508 fn check_selected_tray_icon(&self, selected: TrayIconType) {
509 let items = &self.tray_icon_types;
510 for (item, icon) in items.iter().zip(TrayIconType::ALL) {
511 let should_check = *icon == selected;
512 if should_check != item.checked() {
513 item.set_enabled(true);
515 item.set_checked(should_check);
517 }
518 }
519 }
520 fn check_selected_quick_switch(&self, selected: QuickSwitchMenu) {
521 let items = &self.tray_quick_switch_items;
522 for (item, option) in items.iter().zip(QuickSwitchMenu::ALL) {
523 let should_check = *option == selected;
524 if should_check != item.checked() {
525 item.set_enabled(true);
527 item.set_checked(should_check);
529 }
530 }
531 }
532 fn check_selected_auto_start(&self, selected: AutoStart) {
533 let items = &self.tray_auto_start_items;
534 for (item, option) in items.iter().zip(AutoStart::ALL) {
535 let should_check = *option == selected;
536 if should_check != item.checked() {
537 item.set_enabled(true);
539 item.set_checked(should_check);
541 }
542 }
543 }
544}
545
546#[derive(Default)]
550pub struct FlatSwitchMenu {
551 tray_ui: SystemTrayRef,
552
553 desktop_count: u32,
556
557 tray_virtual_desktops: Vec<nwg::MenuItem>,
559}
560impl FlatSwitchMenu {
561 fn check_current_desktop(&self, current_desktop_index: u32) {
562 let desktops = self.tray_virtual_desktops.as_slice();
563 for (i, desktop) in desktops.iter().rev().enumerate() {
564 let is_current = i == current_desktop_index as usize;
565 let was_checked = desktop.checked();
566 if is_current != was_checked {
567 desktop.set_enabled(true);
569 desktop.set_checked(is_current);
571 }
572 }
573 }
574}
575impl nwg::PartialUi for FlatSwitchMenu {
576 fn build_partial<W: Into<nwg::ControlHandle>>(
577 data: &mut Self,
578 parent: Option<W>,
579 ) -> Result<(), nwg::NwgError> {
580 let parent = parent.map(Into::into).ok_or_else(|| {
581 nwg::NwgError::MenuCreationError("No parent defined for FlatSwitchMenu".to_string())
582 })?;
583 {
584 let tray_desktops = &mut data.tray_virtual_desktops;
585 tray_desktops.clear();
586
587 for i in (1..=data.desktop_count.min(15)).rev() {
588 let mut item = Default::default();
589 nwg::MenuItem::builder()
590 .text(&format!(
591 "Virtual desktop {}{i}",
592 if i < 10 { "&" } else { "" }
593 ))
594 .parent(parent)
595 .build(&mut item)
596 .map_err(|e| {
597 nwg::NwgError::MenuCreationError(format!(
598 "Failed to build menu item for FlatSwitchMenu: {e}"
599 ))
600 })?;
601 tray_desktops.push(item);
602 }
603 }
604
605 if let Some(tray_ui) = data.tray_ui.get() {
608 data.check_current_desktop(tray_ui.desktop_index.get());
609 }
610
611 Ok(())
612 }
613 fn process_event(
614 &self,
615 evt: nwg::Event,
616 _evt_data: &nwg::EventData,
617 handle: nwg::ControlHandle,
618 ) {
619 if let nwg::Event::OnMenuItemSelected = evt {
620 let desktop_ix = self
621 .tray_virtual_desktops
622 .iter()
623 .rev()
624 .position(|d| d.handle == handle);
625 if let Some(clicked_desktop_ix) = desktop_ix {
626 if let Some(tray_ui) = self.tray_ui.get() {
627 tray_ui.switch_desktop(clicked_desktop_ix as u32);
628 }
629 }
630 }
631 }
632}
633impl DynamicUiHooks<SystemTray> for FlatSwitchMenu {
634 fn before_partial_build(
635 &mut self,
636 tray_ui: &Rc<SystemTray>,
637 should_build: &mut bool,
638 ) -> Option<(nwg::ControlHandle, TypeId)> {
639 if tray_ui.settings().get().quick_switch_menu == QuickSwitchMenu::TopMenu {
640 *should_build = false;
641 return None;
642 }
643 self.desktop_count = tray_ui.desktop_count.get();
644 self.tray_ui.set(tray_ui);
645 Some((tray_ui.root().tray_menu.handle, TypeId::of::<TrayRoot>()))
646 }
647 fn need_rebuild(&self, tray_ui: &Rc<SystemTray>) -> bool {
648 if tray_ui.settings().get().quick_switch_menu == QuickSwitchMenu::TopMenu {
649 self.desktop_count != 0 } else {
651 self.desktop_count != tray_ui.desktop_count.get()
652 }
653 }
654}
655impl TrayPlugin for FlatSwitchMenu {
656 fn on_current_desktop_changed(&self, _tray_ui: &Rc<SystemTray>, current_desktop_index: u32) {
657 self.check_current_desktop(current_desktop_index);
658 }
659}
660
661#[derive(Default, nwd::NwgPartial)]
665pub struct BackspaceAsEscapeAlias {}
666impl DynamicUiHooks<SystemTray> for BackspaceAsEscapeAlias {
667 fn before_partial_build(
668 &mut self,
669 _dynamic_ui: &Rc<SystemTray>,
670 _should_build: &mut bool,
671 ) -> Option<(nwg::ControlHandle, TypeId)> {
672 None
673 }
674}
675impl TrayPlugin for BackspaceAsEscapeAlias {
676 fn on_menu_key_press(
677 &self,
678 _tray_ui: &Rc<SystemTray>,
679 key_code: u32,
680 _menu_handle: isize,
681 ) -> Option<MenuKeyPressEffect> {
682 if key_code != 8 {
683 return None;
685 }
686 'simulate_escape: {
687 use windows::Win32::{
688 Foundation::{LPARAM, WPARAM},
689 UI::{
690 Input::KeyboardAndMouse::VK_ESCAPE,
691 WindowsAndMessaging::{SendMessageW, WM_KEYDOWN},
692 },
693 };
694
695 let Some(context_menu_window) = crate::nwg_ext::find_context_menu_window() else {
696 tracing::warn!("Unable to find context menu window");
697 break 'simulate_escape;
698 };
699 unsafe {
700 SendMessageW(
701 context_menu_window,
702 WM_KEYDOWN,
703 WPARAM(usize::from(VK_ESCAPE.0)),
704 LPARAM(0),
705 );
706 }
707 }
708 Some(MenuKeyPressEffect::SelectIndex(0))
709 }
710}
711
712#[derive(Default)]
717pub struct QuickSwitchMenuUiAdapter {
718 tray_ui: SystemTrayRef,
719
720 desktop_count: u32,
723
724 extra_separators: Option<(nwg::MenuSeparator, nwg::MenuSeparator)>,
726
727 parent: nwg::ControlHandle,
728
729 tray_quick_menu_state: crate::quick_switch::QuickSwitchMenu,
730}
731impl nwg::PartialUi for QuickSwitchMenuUiAdapter {
732 fn build_partial<W: Into<nwg::ControlHandle>>(
733 data: &mut Self,
734 parent: Option<W>,
735 ) -> Result<(), nwg::NwgError> {
736 let parent = parent.map(Into::into).ok_or_else(|| {
737 nwg::NwgError::MenuCreationError("No parent defined for quick switch menu".to_string())
738 })?;
739 data.parent = parent;
740 if let Some((first, _)) = &mut data.extra_separators {
741 MenuSeparator::builder().parent(parent).build(first)?;
742 }
743
744 let quick = &mut data.tray_quick_menu_state;
745 quick.clear();
746 quick.create_quick_switch_menu(parent, data.desktop_count + 1);
747
748 if let Some((_, last)) = &mut data.extra_separators {
749 MenuSeparator::builder().parent(parent).build(last)?;
750 }
751 Ok(())
752 }
753 fn process_event(
754 &self,
755 evt: nwg::Event,
756 _evt_data: &nwg::EventData,
757 handle: nwg::ControlHandle,
758 ) {
759 if let nwg::Event::OnMenuItemSelected = evt {
760 let desktop_ix = self.tray_quick_menu_state.get_clicked_desktop_index(handle);
761 if let Some(clicked_desktop_ix) = desktop_ix {
762 if let Some(tray_ui) = self.tray_ui.get() {
763 tray_ui.switch_desktop(clicked_desktop_ix as u32);
764 }
765 }
766 }
767 }
768}
769impl DynamicUiHooks<SystemTray> for QuickSwitchMenuUiAdapter {
770 fn before_partial_build(
771 &mut self,
772 tray_ui: &Rc<SystemTray>,
773 should_build: &mut bool,
774 ) -> Option<(nwg::ControlHandle, TypeId)> {
775 let settings = tray_ui.settings().get();
776 self.tray_quick_menu_state.shortcuts =
777 BTreeMap::clone(&settings.quick_switch_menu_shortcuts);
778 self.tray_quick_menu_state.shortcuts_only_in_root =
779 settings.quick_switch_menu_shortcuts_only_in_root;
780 if settings.quick_switch_menu == QuickSwitchMenu::Disabled {
781 *should_build = false;
782 return None;
783 }
784 self.tray_ui.set(tray_ui);
785 let (parent_handle, parent_id) = if let Some(menu) = tray_ui
786 .dynamic_ui
787 .get_ui::<QuickSwitchTopMenu>()
788 .filter(|top| top.is_built)
789 {
790 (
791 menu.tray_quick_menu.handle,
792 TypeId::of::<QuickSwitchTopMenu>(),
793 )
794 } else {
795 tracing::info!(
796 "No QuickSwitchTopMenu so quick switch menu will be inlined in the root context menu"
797 );
798 self.extra_separators = Some(Default::default());
799 (tray_ui.root().tray_menu.handle, TypeId::of::<TrayRoot>())
800 };
801 self.desktop_count = tray_ui.desktop_count.get();
802 Some((parent_handle, parent_id))
803 }
804 fn need_rebuild(&self, tray_ui: &Rc<SystemTray>) -> bool {
805 let settings = tray_ui.settings().get();
806 let has_moved_menu = if settings.quick_switch_menu == QuickSwitchMenu::Disabled {
807 self.desktop_count != 0
808 } else {
809 self.desktop_count != tray_ui.desktop_count.get()
810 };
811 let has_changed_shortcuts = settings.quick_switch_menu_shortcuts_only_in_root
812 != self.tray_quick_menu_state.shortcuts_only_in_root
813 || *settings.quick_switch_menu_shortcuts != self.tray_quick_menu_state.shortcuts;
814 has_moved_menu || has_changed_shortcuts
815 }
816 fn before_rebuild(&mut self, _tray_ui: &Rc<SystemTray>) {
817 let quick = std::mem::take(&mut self.tray_quick_menu_state);
819 *self = Default::default();
820 self.tray_quick_menu_state = quick;
821 self.tray_quick_menu_state.clear();
822 }
823}
824impl TrayPlugin for QuickSwitchMenuUiAdapter {
825 fn on_menu_key_press(
826 &self,
827 tray_ui: &Rc<SystemTray>,
828 key_code: u32,
829 menu_handle: isize,
830 ) -> Option<MenuKeyPressEffect> {
831 let key = char::from_u32(key_code)?;
832 if key == 'q' || key == 'Q' {
833 let parent = match self.parent {
834 nwg::ControlHandle::Menu(_, h) => h as isize,
835 nwg::ControlHandle::PopMenu(_, h) => h as isize,
836 _ => {
837 tracing::error!("Parent to quick switch menu wasn't a menu");
838 return None;
839 }
840 };
841 if parent != menu_handle {
842 return None; }
844 let item = self
845 .tray_quick_menu_state
846 .first_item_in_submenu(menu_handle)?;
847 return Some(MenuKeyPressEffect::Select(item));
848 }
849 if key != ' ' {
850 return None;
851 }
852 let Some(wanted_ix) = self
853 .tray_quick_menu_state
854 .get_desktop_index_so_far(menu_handle)
855 else {
856 tracing::debug!("Could not find quick switch submenu when pressing space");
857 return None;
858 };
859 tracing::info!(
860 "Pressed space while inside a quick switch context submenu that \
861 would have been opened by pressing the access keys corresponding \
862 to the desktop with the one-based index {}",
863 wanted_ix + 1
864 );
865 tray_ui.switch_desktop(wanted_ix as u32);
866 Some(MenuKeyPressEffect::Close)
867 }
868}
869
870#[derive(Default, nwd::NwgPartial)]
871pub struct BottomMenuItems {
872 tray_ui: SystemTrayRef,
873
874 #[nwg_control]
875 tray_sep1: nwg::MenuSeparator,
876
877 #[nwg_control(text: "Stop &Flashing Windows")]
878 #[nwg_events(OnMenuItemSelected: [Self::stop_flashing_windows])]
879 tray_stop_flashing: nwg::MenuItem,
880
881 #[nwg_control(text: "Configure Filters")]
882 #[nwg_events(OnMenuItemSelected: [Self::open_filter_config])]
883 tray_configure_filters: nwg::MenuItem,
884
885 #[nwg_control(text: "Apply Filters")]
886 #[nwg_events(OnMenuItemSelected: [Self::apply_filters])]
887 tray_apply_filters: nwg::MenuItem,
888
889 #[nwg_control]
890 tray_sep2: nwg::MenuSeparator,
891
892 #[nwg_control(text: "Exit")]
893 #[nwg_events(OnMenuItemSelected: [Self::exit])]
894 tray_exit: nwg::MenuItem,
895}
896impl BottomMenuItems {
898 forward_to_dynamic_ui!(tray_ui => apply_filters, stop_flashing_windows, exit);
899
900 fn open_filter_config(&self) {
901 let Some(tray_ui) = self.tray_ui.get() else {
902 return;
903 };
904 tray_ui.configure_filters(true);
905 }
906}
907impl DynamicUiHooks<SystemTray> for BottomMenuItems {
908 fn before_partial_build(
909 &mut self,
910 tray_ui: &Rc<SystemTray>,
911 _should_build: &mut bool,
912 ) -> Option<(nwg::ControlHandle, TypeId)> {
913 self.tray_ui.set(tray_ui);
914 Some((tray_ui.root().tray_menu.handle, TypeId::of::<TrayRoot>()))
915 }
916}
917impl TrayPlugin for BottomMenuItems {}