app_menu.rs

  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}