1use crate::{Action, App, Platform, SharedString};
2
3/// A menu of the application, either a main menu or a submenu
4pub struct Menu {
5 /// The name of the menu
6 pub name: SharedString,
7
8 /// The items in the menu
9 pub items: Vec<MenuItem>,
10}
11
12impl Menu {
13 /// Create an OwnedMenu from this Menu
14 pub fn owned(self) -> OwnedMenu {
15 OwnedMenu {
16 name: self.name.to_string().into(),
17 items: self.items.into_iter().map(|item| item.owned()).collect(),
18 }
19 }
20}
21
22/// OS menus are menus that are recognized by the operating system
23/// This allows the operating system to provide specialized items for
24/// these menus
25pub struct OsMenu {
26 /// The name of the menu
27 pub name: SharedString,
28
29 /// The type of menu
30 pub menu_type: SystemMenuType,
31}
32
33impl OsMenu {
34 /// Create an OwnedOsMenu from this OsMenu
35 pub fn owned(self) -> OwnedOsMenu {
36 OwnedOsMenu {
37 name: self.name.to_string().into(),
38 menu_type: self.menu_type,
39 }
40 }
41}
42
43/// The type of system menu
44#[derive(Copy, Clone, Eq, PartialEq)]
45pub enum SystemMenuType {
46 /// The 'Services' menu in the Application menu on macOS
47 Services,
48}
49
50/// The different kinds of items that can be in a menu
51pub enum MenuItem {
52 /// A separator between items
53 Separator,
54
55 /// A submenu
56 Submenu(Menu),
57
58 /// A menu, managed by the system (for example, the Services menu on macOS)
59 SystemMenu(OsMenu),
60
61 /// An action that can be performed
62 Action {
63 /// The name of this menu item
64 name: SharedString,
65
66 /// The action to perform when this menu item is selected
67 action: Box<dyn Action>,
68
69 /// The OS Action that corresponds to this action, if any
70 /// See [`OsAction`] for more information
71 os_action: Option<OsAction>,
72
73 /// Whether this action is checked
74 checked: bool,
75 },
76}
77
78impl MenuItem {
79 /// Creates a new menu item that is a separator
80 pub fn separator() -> Self {
81 Self::Separator
82 }
83
84 /// Creates a new menu item that is a submenu
85 pub fn submenu(menu: Menu) -> Self {
86 Self::Submenu(menu)
87 }
88
89 /// Creates a new submenu that is populated by the OS
90 pub fn os_submenu(name: impl Into<SharedString>, menu_type: SystemMenuType) -> Self {
91 Self::SystemMenu(OsMenu {
92 name: name.into(),
93 menu_type,
94 })
95 }
96
97 /// Creates a new menu item that invokes an action
98 pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {
99 Self::Action {
100 name: name.into(),
101 action: Box::new(action),
102 os_action: None,
103 checked: false,
104 }
105 }
106
107 /// Creates a new menu item that invokes an action and has an OS action
108 pub fn os_action(
109 name: impl Into<SharedString>,
110 action: impl Action,
111 os_action: OsAction,
112 ) -> Self {
113 Self::Action {
114 name: name.into(),
115 action: Box::new(action),
116 os_action: Some(os_action),
117 checked: false,
118 }
119 }
120
121 /// Create an OwnedMenuItem from this MenuItem
122 pub fn owned(self) -> OwnedMenuItem {
123 match self {
124 MenuItem::Separator => OwnedMenuItem::Separator,
125 MenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.owned()),
126 MenuItem::Action {
127 name,
128 action,
129 os_action,
130 checked,
131 } => OwnedMenuItem::Action {
132 name: name.into(),
133 action,
134 os_action,
135 checked,
136 },
137 MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()),
138 }
139 }
140
141 /// Set whether this menu item is checked
142 ///
143 /// Only for [`MenuItem::Action`], otherwise, will be ignored
144 pub fn checked(mut self, checked: bool) -> Self {
145 match self {
146 MenuItem::Action {
147 action,
148 os_action,
149 name,
150 ..
151 } => MenuItem::Action {
152 name,
153 action,
154 os_action,
155 checked,
156 },
157 _ => self,
158 }
159 }
160}
161
162/// OS menus are menus that are recognized by the operating system
163/// This allows the operating system to provide specialized items for
164/// these menus
165#[derive(Clone)]
166pub struct OwnedOsMenu {
167 /// The name of the menu
168 pub name: SharedString,
169
170 /// The type of menu
171 pub menu_type: SystemMenuType,
172}
173
174/// A menu of the application, either a main menu or a submenu
175#[derive(Clone)]
176pub struct OwnedMenu {
177 /// The name of the menu
178 pub name: SharedString,
179
180 /// The items in the menu
181 pub items: Vec<OwnedMenuItem>,
182}
183
184/// The different kinds of items that can be in a menu
185pub enum OwnedMenuItem {
186 /// A separator between items
187 Separator,
188
189 /// A submenu
190 Submenu(OwnedMenu),
191
192 /// A menu, managed by the system (for example, the Services menu on macOS)
193 SystemMenu(OwnedOsMenu),
194
195 /// An action that can be performed
196 Action {
197 /// The name of this menu item
198 name: String,
199
200 /// The action to perform when this menu item is selected
201 action: Box<dyn Action>,
202
203 /// The OS Action that corresponds to this action, if any
204 /// See [`OsAction`] for more information
205 os_action: Option<OsAction>,
206
207 /// Whether this action is checked
208 checked: bool,
209 },
210}
211
212impl Clone for OwnedMenuItem {
213 fn clone(&self) -> Self {
214 match self {
215 OwnedMenuItem::Separator => OwnedMenuItem::Separator,
216 OwnedMenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.clone()),
217 OwnedMenuItem::Action {
218 name,
219 action,
220 os_action,
221 checked,
222 } => OwnedMenuItem::Action {
223 name: name.clone(),
224 action: action.boxed_clone(),
225 os_action: *os_action,
226 checked: *checked,
227 },
228 OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),
229 }
230 }
231}
232
233// TODO: As part of the global selections refactor, these should
234// be moved to GPUI-provided actions that make this association
235// without leaking the platform details to GPUI users
236
237/// OS actions are actions that are recognized by the operating system
238/// This allows the operating system to provide specialized behavior for
239/// these actions
240#[derive(Copy, Clone, Eq, PartialEq)]
241pub enum OsAction {
242 /// The 'cut' action
243 Cut,
244
245 /// The 'copy' action
246 Copy,
247
248 /// The 'paste' action
249 Paste,
250
251 /// The 'select all' action
252 SelectAll,
253
254 /// The 'undo' action
255 Undo,
256
257 /// The 'redo' action
258 Redo,
259}
260
261pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &App) {
262 platform.on_will_open_app_menu(Box::new({
263 let cx = cx.to_async();
264 move || {
265 if let Some(app) = cx.app.upgrade() {
266 app.borrow_mut().update(|cx| cx.clear_pending_keystrokes());
267 }
268 }
269 }));
270
271 platform.on_validate_app_menu_command(Box::new({
272 let cx = cx.to_async();
273 move |action| {
274 cx.app
275 .upgrade()
276 .map(|app| app.borrow_mut().update(|cx| cx.is_action_available(action)))
277 .unwrap_or(false)
278 }
279 }));
280
281 platform.on_app_menu_action(Box::new({
282 let cx = cx.to_async();
283 move |action| {
284 if let Some(app) = cx.app.upgrade() {
285 app.borrow_mut().update(|cx| cx.dispatch_action(action));
286 }
287 }
288 }));
289}