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}
75
76impl MenuItem {
77 /// Creates a new menu item that is a separator
78 pub fn separator() -> Self {
79 Self::Separator
80 }
81
82 /// Creates a new menu item that is a submenu
83 pub fn submenu(menu: Menu) -> Self {
84 Self::Submenu(menu)
85 }
86
87 /// Creates a new submenu that is populated by the OS
88 pub fn os_submenu(name: impl Into<SharedString>, menu_type: SystemMenuType) -> Self {
89 Self::SystemMenu(OsMenu {
90 name: name.into(),
91 menu_type,
92 })
93 }
94
95 /// Creates a new menu item that invokes an action
96 pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {
97 Self::Action {
98 name: name.into(),
99 action: Box::new(action),
100 os_action: None,
101 }
102 }
103
104 /// Creates a new menu item that invokes an action and has an OS action
105 pub fn os_action(
106 name: impl Into<SharedString>,
107 action: impl Action,
108 os_action: OsAction,
109 ) -> Self {
110 Self::Action {
111 name: name.into(),
112 action: Box::new(action),
113 os_action: Some(os_action),
114 }
115 }
116
117 /// Create an OwnedMenuItem from this MenuItem
118 pub fn owned(self) -> OwnedMenuItem {
119 match self {
120 MenuItem::Separator => OwnedMenuItem::Separator,
121 MenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.owned()),
122 MenuItem::Action {
123 name,
124 action,
125 os_action,
126 } => OwnedMenuItem::Action {
127 name: name.into(),
128 action,
129 os_action,
130 },
131 MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()),
132 }
133 }
134}
135
136/// OS menus are menus that are recognized by the operating system
137/// This allows the operating system to provide specialized items for
138/// these menus
139#[derive(Clone)]
140pub struct OwnedOsMenu {
141 /// The name of the menu
142 pub name: SharedString,
143
144 /// The type of menu
145 pub menu_type: SystemMenuType,
146}
147
148/// A menu of the application, either a main menu or a submenu
149#[derive(Clone)]
150pub struct OwnedMenu {
151 /// The name of the menu
152 pub name: SharedString,
153
154 /// The items in the menu
155 pub items: Vec<OwnedMenuItem>,
156}
157
158/// The different kinds of items that can be in a menu
159pub enum OwnedMenuItem {
160 /// A separator between items
161 Separator,
162
163 /// A submenu
164 Submenu(OwnedMenu),
165
166 /// A menu, managed by the system (for example, the Services menu on macOS)
167 SystemMenu(OwnedOsMenu),
168
169 /// An action that can be performed
170 Action {
171 /// The name of this menu item
172 name: String,
173
174 /// the action to perform when this menu item is selected
175 action: Box<dyn Action>,
176
177 /// The OS Action that corresponds to this action, if any
178 /// See [`OsAction`] for more information
179 os_action: Option<OsAction>,
180 },
181}
182
183impl Clone for OwnedMenuItem {
184 fn clone(&self) -> Self {
185 match self {
186 OwnedMenuItem::Separator => OwnedMenuItem::Separator,
187 OwnedMenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.clone()),
188 OwnedMenuItem::Action {
189 name,
190 action,
191 os_action,
192 } => OwnedMenuItem::Action {
193 name: name.clone(),
194 action: action.boxed_clone(),
195 os_action: *os_action,
196 },
197 OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),
198 }
199 }
200}
201
202// TODO: As part of the global selections refactor, these should
203// be moved to GPUI-provided actions that make this association
204// without leaking the platform details to GPUI users
205
206/// OS actions are actions that are recognized by the operating system
207/// This allows the operating system to provide specialized behavior for
208/// these actions
209#[derive(Copy, Clone, Eq, PartialEq)]
210pub enum OsAction {
211 /// The 'cut' action
212 Cut,
213
214 /// The 'copy' action
215 Copy,
216
217 /// The 'paste' action
218 Paste,
219
220 /// The 'select all' action
221 SelectAll,
222
223 /// The 'undo' action
224 Undo,
225
226 /// The 'redo' action
227 Redo,
228}
229
230pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &App) {
231 platform.on_will_open_app_menu(Box::new({
232 let cx = cx.to_async();
233 move || {
234 cx.update(|cx| cx.clear_pending_keystrokes()).ok();
235 }
236 }));
237
238 platform.on_validate_app_menu_command(Box::new({
239 let cx = cx.to_async();
240 move |action| {
241 cx.update(|cx| cx.is_action_available(action))
242 .unwrap_or(false)
243 }
244 }));
245
246 platform.on_app_menu_action(Box::new({
247 let cx = cx.to_async();
248 move |action| {
249 cx.update(|cx| cx.dispatch_action(action)).log_err();
250 }
251 }));
252}