Skip to main content

gpui/platform/
app_menu.rs

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}