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}