tauri/menu/
menu.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use 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
18/// Expected submenu id of the Window menu for macOS.
19pub const WINDOW_SUBMENU_ID: &str = "__tauri_window_menu__";
20/// Expected submenu id of the Help menu for macOS.
21pub 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  /// Creates a new menu.
93  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  /// Creates a new menu with the specified id.
110  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  /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally.
128  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  /// Creates a new menu with the specified id and given `items`.
138  /// It calls [`Menu::new`] and [`Menu::append_items`] internally.
139  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  /// Creates a menu filled with default menu items and submenus.
150  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  /// The application handle associated with this type.
256  pub fn app_handle(&self) -> &AppHandle<R> {
257    &self.0.app_handle
258  }
259
260  /// Returns a unique identifier associated with this menu.
261  pub fn id(&self) -> &MenuId {
262    &self.0.id
263  }
264
265  /// Add a menu item to the end of this menu.
266  ///
267  /// ## Platform-specific:
268  ///
269  /// - **macOS:** Only [`Submenu`] can be added to the menu.
270  ///
271  /// [`Submenu`]: super::Submenu
272  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  /// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally.
281  ///
282  /// ## Platform-specific:
283  ///
284  /// - **macOS:** Only [`Submenu`] can be added to the menu
285  ///
286  /// [`Submenu`]: super::Submenu
287  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  /// Add a menu item to the beginning of this menu.
296  ///
297  /// ## Platform-specific:
298  ///
299  /// - **macOS:** Only [`Submenu`] can be added to the menu
300  ///
301  /// [`Submenu`]: super::Submenu
302  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  /// Add menu items to the beginning of this menu. It calls [`Menu::insert_items`] with position of `0` internally.
311  ///
312  /// ## Platform-specific:
313  ///
314  /// - **macOS:** Only [`Submenu`] can be added to the menu
315  ///
316  /// [`Submenu`]: super::Submenu
317  pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
318    self.insert_items(items, 0)
319  }
320
321  /// Insert a menu item at the specified `position` in the menu.
322  ///
323  /// ## Platform-specific:
324  ///
325  /// - **macOS:** Only [`Submenu`] can be added to the menu
326  ///
327  /// [`Submenu`]: super::Submenu
328  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  /// Insert menu items at the specified `position` in the menu.
337  ///
338  /// ## Platform-specific:
339  ///
340  /// - **macOS:** Only [`Submenu`] can be added to the menu
341  ///
342  /// [`Submenu`]: super::Submenu
343  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  /// Remove a menu item from this menu.
352  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  /// Remove the menu item at the specified position from this menu and returns it.
361  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  /// Retrieves the menu item matching the given identifier.
371  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  /// Returns a list of menu items that has been added to this menu.
384  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  /// Set this menu as the application menu.
396  ///
397  /// This is an alias for [`AppHandle::set_menu`].
398  pub fn set_as_app_menu(&self) -> crate::Result<Option<Menu<R>>> {
399    self.0.app_handle.set_menu(self.clone())
400  }
401
402  /// Set this menu as the window menu.
403  ///
404  /// This is an alias for [`Window::set_menu`].
405  pub fn set_as_window_menu(&self, window: &Window<R>) -> crate::Result<Option<Menu<R>>> {
406    window.set_menu(self.clone())
407  }
408}