1use crate::{Action, App, Platform, SharedString};
2use util::ResultExt;
3
4pub struct Menu {
6 pub name: SharedString,
8
9 pub items: Vec<MenuItem>,
11}
12
13impl Menu {
14 pub fn owned(self) -> OwnedMenu {
16 OwnedMenu {
17 name: self.name.to_string().into(),
18 items: self.items.into_iter().map(|item| item.owned()).collect(),
19 }
20 }
21}
22
23pub struct OsMenu {
27 pub name: SharedString,
29
30 pub menu_type: SystemMenuType,
32}
33
34impl OsMenu {
35 pub fn owned(self) -> OwnedOsMenu {
37 OwnedOsMenu {
38 name: self.name.to_string().into(),
39 menu_type: self.menu_type,
40 }
41 }
42}
43
44#[derive(Copy, Clone, Eq, PartialEq)]
46pub enum SystemMenuType {
47 Services,
49}
50
51pub enum MenuItem {
53 Separator,
55
56 Submenu(Menu),
58
59 SystemMenu(OsMenu),
61
62 Action {
64 name: SharedString,
66
67 action: Box<dyn Action>,
69
70 os_action: Option<OsAction>,
73
74 checked: bool,
76 },
77}
78
79impl MenuItem {
80 pub fn separator() -> Self {
82 Self::Separator
83 }
84
85 pub fn submenu(menu: Menu) -> Self {
87 Self::Submenu(menu)
88 }
89
90 pub fn os_submenu(name: impl Into<SharedString>, menu_type: SystemMenuType) -> Self {
92 Self::SystemMenu(OsMenu {
93 name: name.into(),
94 menu_type,
95 })
96 }
97
98 pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {
100 Self::Action {
101 name: name.into(),
102 action: Box::new(action),
103 os_action: None,
104 checked: false,
105 }
106 }
107
108 pub fn os_action(
110 name: impl Into<SharedString>,
111 action: impl Action,
112 os_action: OsAction,
113 ) -> Self {
114 Self::Action {
115 name: name.into(),
116 action: Box::new(action),
117 os_action: Some(os_action),
118 checked: false,
119 }
120 }
121
122 pub fn owned(self) -> OwnedMenuItem {
124 match self {
125 MenuItem::Separator => OwnedMenuItem::Separator,
126 MenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.owned()),
127 MenuItem::Action {
128 name,
129 action,
130 os_action,
131 checked,
132 } => OwnedMenuItem::Action {
133 name: name.into(),
134 action,
135 os_action,
136 checked,
137 },
138 MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()),
139 }
140 }
141
142 pub fn checked(mut self, checked: bool) -> Self {
146 match self {
147 MenuItem::Action {
148 action,
149 os_action,
150 name,
151 ..
152 } => MenuItem::Action {
153 name,
154 action,
155 os_action,
156 checked,
157 },
158 _ => self,
159 }
160 }
161}
162
163#[derive(Clone)]
167pub struct OwnedOsMenu {
168 pub name: SharedString,
170
171 pub menu_type: SystemMenuType,
173}
174
175#[derive(Clone)]
177pub struct OwnedMenu {
178 pub name: SharedString,
180
181 pub items: Vec<OwnedMenuItem>,
183}
184
185pub enum OwnedMenuItem {
187 Separator,
189
190 Submenu(OwnedMenu),
192
193 SystemMenu(OwnedOsMenu),
195
196 Action {
198 name: String,
200
201 action: Box<dyn Action>,
203
204 os_action: Option<OsAction>,
207
208 checked: bool,
210 },
211}
212
213impl Clone for OwnedMenuItem {
214 fn clone(&self) -> Self {
215 match self {
216 OwnedMenuItem::Separator => OwnedMenuItem::Separator,
217 OwnedMenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.clone()),
218 OwnedMenuItem::Action {
219 name,
220 action,
221 os_action,
222 checked,
223 } => OwnedMenuItem::Action {
224 name: name.clone(),
225 action: action.boxed_clone(),
226 os_action: *os_action,
227 checked: *checked,
228 },
229 OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),
230 }
231 }
232}
233
234#[derive(Copy, Clone, Eq, PartialEq)]
242pub enum OsAction {
243 Cut,
245
246 Copy,
248
249 Paste,
251
252 SelectAll,
254
255 Undo,
257
258 Redo,
260}
261
262pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &App) {
263 platform.on_will_open_app_menu(Box::new({
264 let cx = cx.to_async();
265 move || {
266 cx.update(|cx| cx.clear_pending_keystrokes()).ok();
267 }
268 }));
269
270 platform.on_validate_app_menu_command(Box::new({
271 let cx = cx.to_async();
272 move |action| {
273 cx.update(|cx| cx.is_action_available(action))
274 .unwrap_or(false)
275 }
276 }));
277
278 platform.on_app_menu_action(Box::new({
279 let cx = cx.to_async();
280 move |action| {
281 cx.update(|cx| cx.dispatch_action(action)).log_err();
282 }
283 }));
284}