1use std::sync::atomic::AtomicU64;
2
3pub enum MenuEntry {
7 Separator,
8 Item(MenuItem),
9 SubMenu(Menu),
10}
11
12pub struct Menu {
13 pub(crate) popup: bool,
14 pub(crate) item: MenuItem,
15 pub(crate) children: Vec<MenuEntry>,
16}
17
18impl From<Menu> for MenuEntry {
19 fn from(m: Menu) -> MenuEntry {
20 MenuEntry::SubMenu(m)
21 }
22}
23
24impl Menu {
25 pub fn new(title: impl Into<String>) -> Self {
26 Self {
27 popup: false,
28 item: MenuItem::new(title),
29 children: Vec::new(),
30 }
31 }
32
33 pub(crate) fn popup(mut self) -> Self {
34 self.popup = true;
35 self
36 }
37
38 pub fn entry(mut self, entry: impl Into<MenuEntry>) -> Self {
40 self.children.push(entry.into());
41 self
42 }
43
44 pub fn separator(self) -> Self {
46 self.entry(MenuEntry::Separator)
47 }
48
49 #[cfg(any(target_os = "windows", target_os = "macos"))]
50 pub(crate) fn platform_menu(&self) -> muda::Menu {
51 let menu = muda::Menu::new();
52 for entry in &self.children {
53 match entry {
54 MenuEntry::Separator => {
55 let _ = menu.append(&muda::PredefinedMenuItem::separator());
56 }
57 MenuEntry::Item(item) => {
58 let _ = menu.append(&muda::MenuItem::with_id(
59 item.id.clone(),
60 item.title.clone(),
61 item.enabled,
62 None,
63 ));
64 }
65 MenuEntry::SubMenu(floem_menu) => {
66 let _ = menu.append(&floem_menu.platform_submenu());
67 }
68 }
69 }
70 menu
71 }
72
73 #[cfg(any(target_os = "windows", target_os = "macos"))]
74 pub(crate) fn platform_submenu(&self) -> muda::Submenu {
75 let menu = muda::Submenu::new(self.item.title.clone(), self.item.enabled);
76 for entry in &self.children {
77 match entry {
78 MenuEntry::Separator => {
79 let _ = menu.append(&muda::PredefinedMenuItem::separator());
80 }
81 MenuEntry::Item(item) => {
82 let _ = menu.append(&muda::MenuItem::with_id(
83 item.id.clone(),
84 item.title.clone(),
85 item.enabled,
86 None,
87 ));
88 }
89 MenuEntry::SubMenu(floem_menu) => {
90 let _ = menu.append(&floem_menu.platform_submenu());
91 }
92 }
93 }
94 menu
95 }
96}
97
98pub struct MenuItem {
99 pub(crate) id: String,
100 pub(crate) title: String,
101 pub(crate) enabled: bool,
102 pub(crate) action: Option<Box<dyn Fn()>>,
103}
104
105impl From<MenuItem> for MenuEntry {
106 fn from(i: MenuItem) -> MenuEntry {
107 MenuEntry::Item(i)
108 }
109}
110
111impl MenuItem {
112 pub fn new(title: impl Into<String>) -> Self {
113 static COUNTER: AtomicU64 = AtomicU64::new(0);
114 let id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
115 Self {
116 id: id.to_string(),
117 title: title.into(),
118 enabled: true,
119 action: None,
120 }
121 }
122
123 pub fn action(mut self, action: impl Fn() + 'static) -> Self {
124 self.action = Some(Box::new(action));
125 self
126 }
127
128 pub fn enabled(mut self, enabled: bool) -> Self {
129 self.enabled = enabled;
130 self
131 }
132}