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