1use crate::{Action, App, Platform, SharedString};
2use util::ResultExt;
3
4/// A menu of the application, either a main menu or a submenu
5pub struct Menu {
6 /// The name of the menu
7 pub name: SharedString,
8
9 /// The items in the menu
10 pub items: Vec<MenuItem>,
11}
12
13impl Menu {
14 /// Create an OwnedMenu from this Menu
15 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
23/// OS menus are menus that are recognized by the operating system
24/// This allows the operating system to provide specialized items for
25/// these menus
26pub struct OsMenu {
27 /// The name of the menu
28 pub name: SharedString,
29
30 /// The type of menu
31 pub menu_type: SystemMenuType,
32}
33
34impl OsMenu {
35 /// Create an OwnedOsMenu from this OsMenu
36 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/// The type of system menu
45#[derive(Copy, Clone, Eq, PartialEq)]
46pub enum SystemMenuType {
47 /// The 'Services' menu in the Application menu on macOS
48 Services,
49}
50
51/// The different kinds of items that can be in a menu
52pub enum MenuItem {
53 /// A separator between items
54 Separator,
55
56 /// A submenu
57 Submenu(Menu),
58
59 /// A menu, managed by the system (for example, the Services menu on macOS)
60 SystemMenu(OsMenu),
61
62 /// An action that can be performed
63 Action {
64 /// The name of this menu item
65 name: SharedString,
66
67 /// The action to perform when this menu item is selected
68 action: Box<dyn Action>,
69
70 /// The OS Action that corresponds to this action, if any
71 /// See [`OsAction`] for more information
72 os_action: Option<OsAction>,
73
74 /// Whether this action is checked
75 checked: bool,
76 },
77}
78
79impl MenuItem {
80 /// Creates a new menu item that is a separator
81 pub fn separator() -> Self {
82 Self::Separator
83 }
84
85 /// Creates a new menu item that is a submenu
86 pub fn submenu(menu: Menu) -> Self {
87 Self::Submenu(menu)
88 }
89
90 /// Creates a new submenu that is populated by the OS
91 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 /// Creates a new menu item that invokes an action
99 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 /// Creates a new menu item that invokes an action and has an OS action
109 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 /// Create an OwnedMenuItem from this MenuItem
123 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 /// Set whether this menu item is checked
143 ///
144 /// Only for [`MenuItem::Action`], otherwise, will be ignored
145 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/// OS menus are menus that are recognized by the operating system
164/// This allows the operating system to provide specialized items for
165/// these menus
166#[derive(Clone)]
167pub struct OwnedOsMenu {
168 /// The name of the menu
169 pub name: SharedString,
170
171 /// The type of menu
172 pub menu_type: SystemMenuType,
173}
174
175/// A menu of the application, either a main menu or a submenu
176#[derive(Clone)]
177pub struct OwnedMenu {
178 /// The name of the menu
179 pub name: SharedString,
180
181 /// The items in the menu
182 pub items: Vec<OwnedMenuItem>,
183}
184
185/// The different kinds of items that can be in a menu
186pub enum OwnedMenuItem {
187 /// A separator between items
188 Separator,
189
190 /// A submenu
191 Submenu(OwnedMenu),
192
193 /// A menu, managed by the system (for example, the Services menu on macOS)
194 SystemMenu(OwnedOsMenu),
195
196 /// An action that can be performed
197 Action {
198 /// The name of this menu item
199 name: String,
200
201 /// The action to perform when this menu item is selected
202 action: Box<dyn Action>,
203
204 /// The OS Action that corresponds to this action, if any
205 /// See [`OsAction`] for more information
206 os_action: Option<OsAction>,
207
208 /// Whether this action is checked
209 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// TODO: As part of the global selections refactor, these should
235// be moved to GPUI-provided actions that make this association
236// without leaking the platform details to GPUI users
237
238/// OS actions are actions that are recognized by the operating system
239/// This allows the operating system to provide specialized behavior for
240/// these actions
241#[derive(Copy, Clone, Eq, PartialEq)]
242pub enum OsAction {
243 /// The 'cut' action
244 Cut,
245
246 /// The 'copy' action
247 Copy,
248
249 /// The 'paste' action
250 Paste,
251
252 /// The 'select all' action
253 SelectAll,
254
255 /// The 'undo' action
256 Undo,
257
258 /// The 'redo' action
259 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}