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}