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}