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