app_menu.rs

  1use std::borrow::Cow;
  2
  3use crate::{Action, AppContext, Platform};
  4use util::ResultExt;
  5
  6/// A menu of the application, either a main menu or a submenu
  7pub struct Menu<'a> {
  8    /// The name of the menu
  9    pub name: &'a str,
 10
 11    /// The items in the menu
 12    pub items: Vec<MenuItem<'a>>,
 13}
 14
 15impl<'a> Menu<'a> {
 16    /// Create an OwnedMenu from this Menu
 17    pub fn owned(self) -> OwnedMenu {
 18        OwnedMenu {
 19            name: self.name.to_string().into(),
 20            items: self.items.into_iter().map(|item| item.owned()).collect(),
 21        }
 22    }
 23}
 24
 25/// The different kinds of items that can be in a menu
 26pub enum MenuItem<'a> {
 27    /// A separator between items
 28    Separator,
 29
 30    /// A submenu
 31    Submenu(Menu<'a>),
 32
 33    /// An action that can be performed
 34    Action {
 35        /// The name of this menu item
 36        name: &'a str,
 37
 38        /// the action to perform when this menu item is selected
 39        action: Box<dyn Action>,
 40
 41        /// The OS Action that corresponds to this action, if any
 42        /// See [`OsAction`] for more information
 43        os_action: Option<OsAction>,
 44    },
 45}
 46
 47impl<'a> MenuItem<'a> {
 48    /// Creates a new menu item that is a separator
 49    pub fn separator() -> Self {
 50        Self::Separator
 51    }
 52
 53    /// Creates a new menu item that is a submenu
 54    pub fn submenu(menu: Menu<'a>) -> Self {
 55        Self::Submenu(menu)
 56    }
 57
 58    /// Creates a new menu item that invokes an action
 59    pub fn action(name: &'a str, action: impl Action) -> Self {
 60        Self::Action {
 61            name,
 62            action: Box::new(action),
 63            os_action: None,
 64        }
 65    }
 66
 67    /// Creates a new menu item that invokes an action and has an OS action
 68    pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
 69        Self::Action {
 70            name,
 71            action: Box::new(action),
 72            os_action: Some(os_action),
 73        }
 74    }
 75
 76    /// Create an OwnedMenuItem from this MenuItem
 77    pub fn owned(self) -> OwnedMenuItem {
 78        match self {
 79            MenuItem::Separator => OwnedMenuItem::Separator,
 80            MenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.owned()),
 81            MenuItem::Action {
 82                name,
 83                action,
 84                os_action,
 85            } => OwnedMenuItem::Action {
 86                name: name.into(),
 87                action,
 88                os_action,
 89            },
 90        }
 91    }
 92}
 93
 94/// A menu of the application, either a main menu or a submenu
 95#[derive(Clone)]
 96pub struct OwnedMenu {
 97    /// The name of the menu
 98    pub name: Cow<'static, str>,
 99
100    /// The items in the menu
101    pub items: Vec<OwnedMenuItem>,
102}
103
104/// The different kinds of items that can be in a menu
105pub enum OwnedMenuItem {
106    /// A separator between items
107    Separator,
108
109    /// A submenu
110    Submenu(OwnedMenu),
111
112    /// An action that can be performed
113    Action {
114        /// The name of this menu item
115        name: String,
116
117        /// the action to perform when this menu item is selected
118        action: Box<dyn Action>,
119
120        /// The OS Action that corresponds to this action, if any
121        /// See [`OsAction`] for more information
122        os_action: Option<OsAction>,
123    },
124}
125
126impl Clone for OwnedMenuItem {
127    fn clone(&self) -> Self {
128        match self {
129            OwnedMenuItem::Separator => OwnedMenuItem::Separator,
130            OwnedMenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.clone()),
131            OwnedMenuItem::Action {
132                name,
133                action,
134                os_action,
135            } => OwnedMenuItem::Action {
136                name: name.clone(),
137                action: action.boxed_clone(),
138                os_action: *os_action,
139            },
140        }
141    }
142}
143
144// TODO: As part of the global selections refactor, these should
145// be moved to GPUI-provided actions that make this association
146// without leaking the platform details to GPUI users
147
148/// OS actions are actions that are recognized by the operating system
149/// This allows the operating system to provide specialized behavior for
150/// these actions
151#[derive(Copy, Clone, Eq, PartialEq)]
152pub enum OsAction {
153    /// The 'cut' action
154    Cut,
155
156    /// The 'copy' action
157    Copy,
158
159    /// The 'paste' action
160    Paste,
161
162    /// The 'select all' action
163    SelectAll,
164
165    /// The 'undo' action
166    Undo,
167
168    /// The 'redo' action
169    Redo,
170}
171
172pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &mut AppContext) {
173    platform.on_will_open_app_menu(Box::new({
174        let cx = cx.to_async();
175        move || {
176            cx.update(|cx| cx.clear_pending_keystrokes()).ok();
177        }
178    }));
179
180    platform.on_validate_app_menu_command(Box::new({
181        let cx = cx.to_async();
182        move |action| {
183            cx.update(|cx| cx.is_action_available(action))
184                .unwrap_or(false)
185        }
186    }));
187
188    platform.on_app_menu_action(Box::new({
189        let cx = cx.to_async();
190        move |action| {
191            cx.update(|cx| cx.dispatch_action(action)).log_err();
192        }
193    }));
194}