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        /// 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}