1use std::sync::Arc;
6
7use super::run_item_main_thread;
8use super::sealed::ContextMenuBase;
9use super::{
10 AboutMetadata, IsMenuItem, Menu, MenuInner, MenuItemKind, PredefinedMenuItem, Submenu,
11};
12use crate::run_main_thread;
13use crate::Window;
14use crate::{AppHandle, Manager, Position, Runtime};
15use muda::ContextMenu;
16use muda::MenuId;
17
18pub const WINDOW_SUBMENU_ID: &str = "__tauri_window_menu__";
20pub const HELP_SUBMENU_ID: &str = "__tauri_help_menu__";
22
23impl<R: Runtime> super::ContextMenu for Menu<R> {
24 #[cfg(target_os = "windows")]
25 fn hpopupmenu(&self) -> crate::Result<isize> {
26 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().hpopupmenu())
27 }
28
29 fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {
30 self.popup_inner(window, None::<Position>)
31 }
32
33 fn popup_at<T: Runtime, P: Into<Position>>(
34 &self,
35 window: Window<T>,
36 position: P,
37 ) -> crate::Result<()> {
38 self.popup_inner(window, Some(position))
39 }
40}
41
42impl<R: Runtime> ContextMenuBase for Menu<R> {
43 fn popup_inner<T: Runtime, P: Into<crate::Position>>(
44 &self,
45 window: crate::Window<T>,
46 position: Option<P>,
47 ) -> crate::Result<()> {
48 let position = position.map(Into::into);
49 run_item_main_thread!(self, move |self_: Self| {
50 #[cfg(target_os = "macos")]
51 if let Ok(view) = window.ns_view() {
52 unsafe {
53 self_
54 .inner()
55 .show_context_menu_for_nsview(view as _, position);
56 }
57 }
58
59 #[cfg(any(
60 target_os = "linux",
61 target_os = "dragonfly",
62 target_os = "freebsd",
63 target_os = "netbsd",
64 target_os = "openbsd"
65 ))]
66 if let Ok(w) = window.gtk_window() {
67 self_
68 .inner()
69 .show_context_menu_for_gtk_window(w.as_ref(), position);
70 }
71
72 #[cfg(windows)]
73 if let Ok(hwnd) = window.hwnd() {
74 unsafe {
75 self_
76 .inner()
77 .show_context_menu_for_hwnd(hwnd.0 as _, position);
78 }
79 }
80 })
81 }
82 fn inner_context(&self) -> &dyn muda::ContextMenu {
83 (*self.0).as_ref()
84 }
85
86 fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {
87 Box::new((*self.0).as_ref().clone())
88 }
89}
90
91impl<R: Runtime> Menu<R> {
92 pub fn new<M: Manager<R>>(manager: &M) -> crate::Result<Self> {
94 let handle = manager.app_handle();
95 let app_handle = handle.clone();
96
97 let menu = run_main_thread!(handle, || {
98 let menu = muda::Menu::new();
99 MenuInner {
100 id: menu.id().clone(),
101 inner: Some(menu),
102 app_handle,
103 }
104 })?;
105
106 Ok(Self(Arc::new(menu)))
107 }
108
109 pub fn with_id<M: Manager<R>, I: Into<MenuId>>(manager: &M, id: I) -> crate::Result<Self> {
111 let handle = manager.app_handle();
112 let app_handle = handle.clone();
113
114 let id = id.into();
115 let menu = run_main_thread!(handle, || {
116 let menu = muda::Menu::with_id(id.clone());
117 MenuInner {
118 id,
119 inner: Some(menu),
120 app_handle,
121 }
122 })?;
123
124 Ok(Self(Arc::new(menu)))
125 }
126
127 pub fn with_items<M: Manager<R>>(
129 manager: &M,
130 items: &[&dyn IsMenuItem<R>],
131 ) -> crate::Result<Self> {
132 let menu = Self::new(manager)?;
133 menu.append_items(items)?;
134 Ok(menu)
135 }
136
137 pub fn with_id_and_items<M: Manager<R>, I: Into<MenuId>>(
140 manager: &M,
141 id: I,
142 items: &[&dyn IsMenuItem<R>],
143 ) -> crate::Result<Self> {
144 let menu = Self::with_id(manager, id)?;
145 menu.append_items(items)?;
146 Ok(menu)
147 }
148
149 pub fn default(app_handle: &AppHandle<R>) -> crate::Result<Self> {
151 let pkg_info = app_handle.package_info();
152 let config = app_handle.config();
153 let about_metadata = AboutMetadata {
154 name: Some(pkg_info.name.clone()),
155 version: Some(pkg_info.version.to_string()),
156 copyright: config.bundle.copyright.clone(),
157 authors: config.bundle.publisher.clone().map(|p| vec![p]),
158 ..Default::default()
159 };
160
161 let window_menu = Submenu::with_id_and_items(
162 app_handle,
163 WINDOW_SUBMENU_ID,
164 "Window",
165 true,
166 &[
167 &PredefinedMenuItem::minimize(app_handle, None)?,
168 &PredefinedMenuItem::maximize(app_handle, None)?,
169 #[cfg(target_os = "macos")]
170 &PredefinedMenuItem::separator(app_handle)?,
171 &PredefinedMenuItem::close_window(app_handle, None)?,
172 ],
173 )?;
174
175 let help_menu = Submenu::with_id_and_items(
176 app_handle,
177 HELP_SUBMENU_ID,
178 "Help",
179 true,
180 &[
181 #[cfg(not(target_os = "macos"))]
182 &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,
183 ],
184 )?;
185
186 let menu = Menu::with_items(
187 app_handle,
188 &[
189 #[cfg(target_os = "macos")]
190 &Submenu::with_items(
191 app_handle,
192 pkg_info.name.clone(),
193 true,
194 &[
195 &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,
196 &PredefinedMenuItem::separator(app_handle)?,
197 &PredefinedMenuItem::services(app_handle, None)?,
198 &PredefinedMenuItem::separator(app_handle)?,
199 &PredefinedMenuItem::hide(app_handle, None)?,
200 &PredefinedMenuItem::hide_others(app_handle, None)?,
201 &PredefinedMenuItem::separator(app_handle)?,
202 &PredefinedMenuItem::quit(app_handle, None)?,
203 ],
204 )?,
205 #[cfg(not(any(
206 target_os = "linux",
207 target_os = "dragonfly",
208 target_os = "freebsd",
209 target_os = "netbsd",
210 target_os = "openbsd"
211 )))]
212 &Submenu::with_items(
213 app_handle,
214 "File",
215 true,
216 &[
217 &PredefinedMenuItem::close_window(app_handle, None)?,
218 #[cfg(not(target_os = "macos"))]
219 &PredefinedMenuItem::quit(app_handle, None)?,
220 ],
221 )?,
222 &Submenu::with_items(
223 app_handle,
224 "Edit",
225 true,
226 &[
227 &PredefinedMenuItem::undo(app_handle, None)?,
228 &PredefinedMenuItem::redo(app_handle, None)?,
229 &PredefinedMenuItem::separator(app_handle)?,
230 &PredefinedMenuItem::cut(app_handle, None)?,
231 &PredefinedMenuItem::copy(app_handle, None)?,
232 &PredefinedMenuItem::paste(app_handle, None)?,
233 &PredefinedMenuItem::select_all(app_handle, None)?,
234 ],
235 )?,
236 #[cfg(target_os = "macos")]
237 &Submenu::with_items(
238 app_handle,
239 "View",
240 true,
241 &[&PredefinedMenuItem::fullscreen(app_handle, None)?],
242 )?,
243 &window_menu,
244 &help_menu,
245 ],
246 )?;
247
248 Ok(menu)
249 }
250
251 pub(crate) fn inner(&self) -> &muda::Menu {
252 (*self.0).as_ref()
253 }
254
255 pub fn app_handle(&self) -> &AppHandle<R> {
257 &self.0.app_handle
258 }
259
260 pub fn id(&self) -> &MenuId {
262 &self.0.id
263 }
264
265 pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
273 let kind = item.kind();
274 run_item_main_thread!(self, |self_: Self| {
275 (*self_.0).as_ref().append(kind.inner().inner_muda())
276 })?
277 .map_err(Into::into)
278 }
279
280 pub fn append_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
288 for item in items {
289 self.append(*item)?
290 }
291
292 Ok(())
293 }
294
295 pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
303 let kind = item.kind();
304 run_item_main_thread!(self, |self_: Self| {
305 (*self_.0).as_ref().prepend(kind.inner().inner_muda())
306 })?
307 .map_err(Into::into)
308 }
309
310 pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
318 self.insert_items(items, 0)
319 }
320
321 pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
329 let kind = item.kind();
330 run_item_main_thread!(self, |self_: Self| (*self_.0)
331 .as_ref()
332 .insert(kind.inner().inner_muda(), position))?
333 .map_err(Into::into)
334 }
335
336 pub fn insert_items(&self, items: &[&dyn IsMenuItem<R>], position: usize) -> crate::Result<()> {
344 for (i, item) in items.iter().enumerate() {
345 self.insert(*item, position + i)?
346 }
347
348 Ok(())
349 }
350
351 pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
353 let kind = item.kind();
354 run_item_main_thread!(self, |self_: Self| {
355 (*self_.0).as_ref().remove(kind.inner().inner_muda())
356 })?
357 .map_err(Into::into)
358 }
359
360 pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {
362 run_item_main_thread!(self, |self_: Self| {
363 (*self_.0)
364 .as_ref()
365 .remove_at(position)
366 .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))
367 })
368 }
369
370 pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>
372 where
373 I: ?Sized,
374 MenuId: PartialEq<&'a I>,
375 {
376 self
377 .items()
378 .unwrap_or_default()
379 .into_iter()
380 .find(|i| i.id() == &id)
381 }
382
383 pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
385 run_item_main_thread!(self, |self_: Self| {
386 (*self_.0)
387 .as_ref()
388 .items()
389 .into_iter()
390 .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))
391 .collect::<Vec<_>>()
392 })
393 }
394
395 pub fn set_as_app_menu(&self) -> crate::Result<Option<Menu<R>>> {
399 self.0.app_handle.set_menu(self.clone())
400 }
401
402 pub fn set_as_window_menu(&self, window: &Window<R>) -> crate::Result<Option<Menu<R>>> {
406 window.set_menu(self.clone())
407 }
408}