tauri/menu/
mod.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Menu types and utilities.
6
7mod builders;
8mod check;
9mod icon;
10#[allow(clippy::module_inception)]
11mod menu;
12mod normal;
13pub(crate) mod plugin;
14mod predefined;
15mod submenu;
16use std::sync::Arc;
17
18pub use builders::*;
19pub use menu::{HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
20use serde::{Deserialize, Serialize};
21
22use crate::{image::Image, AppHandle, Runtime};
23pub use muda::MenuId;
24
25macro_rules! run_item_main_thread {
26  ($self:ident, $ex:expr) => {{
27    use std::sync::mpsc::channel;
28    let (tx, rx) = channel();
29    let self_ = $self.clone();
30    let task = move || {
31      let f = $ex;
32      let _ = tx.send(f(self_));
33    };
34    $self
35      .app_handle()
36      .run_on_main_thread(task)
37      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
38  }};
39}
40
41pub(crate) use run_item_main_thread;
42
43/// Describes a menu event emitted when a menu item is activated
44#[derive(Debug, Clone, Serialize)]
45pub struct MenuEvent {
46  /// Id of the menu item which triggered this event
47  pub id: MenuId,
48}
49
50impl MenuEvent {
51  /// Returns the id of the menu item which triggered this event
52  pub fn id(&self) -> &MenuId {
53    &self.id
54  }
55}
56
57impl From<muda::MenuEvent> for MenuEvent {
58  fn from(value: muda::MenuEvent) -> Self {
59    Self { id: value.id }
60  }
61}
62
63macro_rules! gen_wrappers {
64  (
65    $(
66      $(#[$attr:meta])*
67      $type:ident($inner:ident$(, $kind:ident)?)
68    ),*
69  ) => {
70    $(
71      #[tauri_macros::default_runtime(crate::Wry, wry)]
72      pub(crate) struct $inner<R: $crate::Runtime> {
73        id: $crate::menu::MenuId,
74        inner: ::std::option::Option<::muda::$type>,
75        app_handle: $crate::AppHandle<R>,
76      }
77
78      /// # Safety
79      ///
80      /// We make sure it always runs on the main thread.
81      unsafe impl<R: $crate::Runtime> Sync for $inner<R> {}
82      unsafe impl<R: $crate::Runtime> Send for $inner<R> {}
83
84      impl<R: Runtime> $crate::Resource for $type<R> {}
85
86      impl<R: $crate::Runtime> Clone for $inner<R> {
87        fn clone(&self) -> Self {
88          Self {
89            id: self.id.clone(),
90            inner: self.inner.clone(),
91            app_handle: self.app_handle.clone(),
92          }
93        }
94      }
95
96      impl<R: Runtime> Drop for $inner<R> {
97        fn drop(&mut self) {
98          let inner = self.inner.take();
99          // SAFETY: inner was created on main thread and is being dropped on main thread
100          let inner = $crate::UnsafeSend(inner);
101          let _ = self.app_handle.run_on_main_thread(move || {
102            drop(inner.take());
103          });
104        }
105      }
106
107      impl<R: Runtime> AsRef<::muda::$type> for $inner<R> {
108        fn as_ref(&self) -> &::muda::$type {
109          self.inner.as_ref().unwrap()
110        }
111      }
112
113
114      $(#[$attr])*
115      pub struct $type<R: $crate::Runtime>(::std::sync::Arc<$inner<R>>);
116
117      impl<R: $crate::Runtime> Clone for $type<R> {
118        fn clone(&self) -> Self {
119          Self(self.0.clone())
120        }
121      }
122
123      $(
124        impl<R: $crate::Runtime> $crate::menu::sealed::IsMenuItemBase for $type<R> {
125          fn inner_muda(&self) -> &dyn muda::IsMenuItem {
126            (*self.0).as_ref()
127          }
128        }
129
130        impl<R: $crate::Runtime> $crate::menu::IsMenuItem<R> for $type<R> {
131          fn kind(&self) -> MenuItemKind<R> {
132            MenuItemKind::$kind(self.clone())
133          }
134
135          fn id(&self) -> &MenuId {
136            &self.0.id
137          }
138        }
139      )*
140    )*
141  };
142}
143
144gen_wrappers!(
145  /// A type that is either a menu bar on the window
146  /// on Windows and Linux or as a global menu in the menubar on macOS.
147  ///
148  /// ## Platform-specific:
149  ///
150  /// - **macOS**: if using [`Menu`] for the global menubar, it can only contain [`Submenu`]s
151  Menu(MenuInner),
152  /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
153  MenuItem(MenuItemInner, MenuItem),
154  /// A type that is a submenu inside a [`Menu`] or [`Submenu`]
155  Submenu(SubmenuInner, Submenu),
156  /// A predefined (native) menu item which has a predefined behavior by the OS or by this crate.
157  PredefinedMenuItem(PredefinedMenuItemInner, Predefined),
158  /// A menu item inside a [`Menu`] or [`Submenu`]
159  /// and usually contains a text and a check mark or a similar toggle
160  /// that corresponds to a checked and unchecked states.
161  CheckMenuItem(CheckMenuItemInner, Check),
162  /// A menu item inside a [`Menu`] or [`Submenu`]
163  /// and usually contains an icon and a text.
164  IconMenuItem(IconMenuItemInner, Icon)
165);
166
167/// Application metadata for the [`PredefinedMenuItem::about`].
168#[derive(Debug, Clone, Default)]
169pub struct AboutMetadata<'a> {
170  /// Sets the application name.
171  pub name: Option<String>,
172  /// The application version.
173  pub version: Option<String>,
174  /// The short version, e.g. "1.0".
175  ///
176  /// ## Platform-specific
177  ///
178  /// - **Windows / Linux:** Appended to the end of `version` in parentheses.
179  pub short_version: Option<String>,
180  /// The authors of the application.
181  ///
182  /// ## Platform-specific
183  ///
184  /// - **macOS:** Unsupported.
185  pub authors: Option<Vec<String>>,
186  /// Application comments.
187  ///
188  /// ## Platform-specific
189  ///
190  /// - **macOS:** Unsupported.
191  pub comments: Option<String>,
192  /// The copyright of the application.
193  pub copyright: Option<String>,
194  /// The license of the application.
195  ///
196  /// ## Platform-specific
197  ///
198  /// - **macOS:** Unsupported.
199  pub license: Option<String>,
200  /// The application website.
201  ///
202  /// ## Platform-specific
203  ///
204  /// - **macOS:** Unsupported.
205  pub website: Option<String>,
206  /// The website label.
207  ///
208  /// ## Platform-specific
209  ///
210  /// - **macOS:** Unsupported.
211  pub website_label: Option<String>,
212  /// The credits.
213  ///
214  /// ## Platform-specific
215  ///
216  /// - **Windows / Linux:** Unsupported.
217  pub credits: Option<String>,
218  /// The application icon.
219  ///
220  /// ## Platform-specific
221  ///
222  /// - **Windows:** Unsupported.
223  pub icon: Option<Image<'a>>,
224}
225
226/// A builder type for [`AboutMetadata`].
227#[derive(Clone, Debug, Default)]
228pub struct AboutMetadataBuilder<'a>(AboutMetadata<'a>);
229
230impl<'a> AboutMetadataBuilder<'a> {
231  /// Create a new about metadata builder.
232  pub fn new() -> Self {
233    Default::default()
234  }
235
236  /// Sets the application name.
237  pub fn name<S: Into<String>>(mut self, name: Option<S>) -> Self {
238    self.0.name = name.map(|s| s.into());
239    self
240  }
241  /// Sets the application version.
242  pub fn version<S: Into<String>>(mut self, version: Option<S>) -> Self {
243    self.0.version = version.map(|s| s.into());
244    self
245  }
246  /// Sets the short version, e.g. "1.0".
247  ///
248  /// ## Platform-specific
249  ///
250  /// - **Windows / Linux:** Appended to the end of `version` in parentheses.
251  pub fn short_version<S: Into<String>>(mut self, short_version: Option<S>) -> Self {
252    self.0.short_version = short_version.map(|s| s.into());
253    self
254  }
255  /// Sets the authors of the application.
256  ///
257  /// ## Platform-specific
258  ///
259  /// - **macOS:** Unsupported.
260  pub fn authors(mut self, authors: Option<Vec<String>>) -> Self {
261    self.0.authors = authors;
262    self
263  }
264  /// Application comments.
265  ///
266  /// ## Platform-specific
267  ///
268  /// - **macOS:** Unsupported.
269  pub fn comments<S: Into<String>>(mut self, comments: Option<S>) -> Self {
270    self.0.comments = comments.map(|s| s.into());
271    self
272  }
273  /// Sets the copyright of the application.
274  pub fn copyright<S: Into<String>>(mut self, copyright: Option<S>) -> Self {
275    self.0.copyright = copyright.map(|s| s.into());
276    self
277  }
278  /// Sets the license of the application.
279  ///
280  /// ## Platform-specific
281  ///
282  /// - **macOS:** Unsupported.
283  pub fn license<S: Into<String>>(mut self, license: Option<S>) -> Self {
284    self.0.license = license.map(|s| s.into());
285    self
286  }
287  /// Sets the application website.
288  ///
289  /// ## Platform-specific
290  ///
291  /// - **macOS:** Unsupported.
292  pub fn website<S: Into<String>>(mut self, website: Option<S>) -> Self {
293    self.0.website = website.map(|s| s.into());
294    self
295  }
296  /// Sets the website label.
297  ///
298  /// ## Platform-specific
299  ///
300  /// - **macOS:** Unsupported.
301  pub fn website_label<S: Into<String>>(mut self, website_label: Option<S>) -> Self {
302    self.0.website_label = website_label.map(|s| s.into());
303    self
304  }
305  /// Sets the credits.
306  ///
307  /// ## Platform-specific
308  ///
309  /// - **Windows / Linux:** Unsupported.
310  pub fn credits<S: Into<String>>(mut self, credits: Option<S>) -> Self {
311    self.0.credits = credits.map(|s| s.into());
312    self
313  }
314  /// Sets the application icon.
315  ///
316  /// ## Platform-specific
317  ///
318  /// - **Windows:** Unsupported.
319  pub fn icon(mut self, icon: Option<Image<'a>>) -> Self {
320    self.0.icon = icon;
321    self
322  }
323
324  /// Construct the final [`AboutMetadata`]
325  pub fn build(self) -> AboutMetadata<'a> {
326    self.0
327  }
328}
329
330impl TryFrom<AboutMetadata<'_>> for muda::AboutMetadata {
331  type Error = crate::Error;
332
333  fn try_from(value: AboutMetadata<'_>) -> Result<Self, Self::Error> {
334    let icon = match value.icon {
335      Some(i) => Some(i.try_into()?),
336      None => None,
337    };
338
339    Ok(Self {
340      authors: value.authors,
341      name: value.name,
342      version: value.version,
343      short_version: value.short_version,
344      comments: value.comments,
345      copyright: value.copyright,
346      license: value.license,
347      website: value.website,
348      website_label: value.website_label,
349      credits: value.credits,
350      icon,
351    })
352  }
353}
354
355/// A native Icon to be used for the menu item
356///
357/// ## Platform-specific:
358///
359/// - **Windows / Linux**: Unsupported.
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
361pub enum NativeIcon {
362  /// An add item template image.
363  Add,
364  /// Advanced preferences toolbar icon for the preferences window.
365  Advanced,
366  /// A Bluetooth template image.
367  Bluetooth,
368  /// Bookmarks image suitable for a template.
369  Bookmarks,
370  /// A caution image.
371  Caution,
372  /// A color panel toolbar icon.
373  ColorPanel,
374  /// A column view mode template image.
375  ColumnView,
376  /// A computer icon.
377  Computer,
378  /// An enter full-screen mode template image.
379  EnterFullScreen,
380  /// Permissions for all users.
381  Everyone,
382  /// An exit full-screen mode template image.
383  ExitFullScreen,
384  /// A cover flow view mode template image.
385  FlowView,
386  /// A folder image.
387  Folder,
388  /// A burnable folder icon.
389  FolderBurnable,
390  /// A smart folder icon.
391  FolderSmart,
392  /// A link template image.
393  FollowLinkFreestanding,
394  /// A font panel toolbar icon.
395  FontPanel,
396  /// A `go back` template image.
397  GoLeft,
398  /// A `go forward` template image.
399  GoRight,
400  /// Home image suitable for a template.
401  Home,
402  /// An iChat Theater template image.
403  IChatTheater,
404  /// An icon view mode template image.
405  IconView,
406  /// An information toolbar icon.
407  Info,
408  /// A template image used to denote invalid data.
409  InvalidDataFreestanding,
410  /// A generic left-facing triangle template image.
411  LeftFacingTriangle,
412  /// A list view mode template image.
413  ListView,
414  /// A locked padlock template image.
415  LockLocked,
416  /// An unlocked padlock template image.
417  LockUnlocked,
418  /// A horizontal dash, for use in menus.
419  MenuMixedState,
420  /// A check mark template image, for use in menus.
421  MenuOnState,
422  /// A MobileMe icon.
423  MobileMe,
424  /// A drag image for multiple items.
425  MultipleDocuments,
426  /// A network icon.
427  Network,
428  /// A path button template image.
429  Path,
430  /// General preferences toolbar icon for the preferences window.
431  PreferencesGeneral,
432  /// A Quick Look template image.
433  QuickLook,
434  /// A refresh template image.
435  RefreshFreestanding,
436  /// A refresh template image.
437  Refresh,
438  /// A remove item template image.
439  Remove,
440  /// A reveal contents template image.
441  RevealFreestanding,
442  /// A generic right-facing triangle template image.
443  RightFacingTriangle,
444  /// A share view template image.
445  Share,
446  /// A slideshow template image.
447  Slideshow,
448  /// A badge for a `smart` item.
449  SmartBadge,
450  /// Small green indicator, similar to iChat's available image.
451  StatusAvailable,
452  /// Small clear indicator.
453  StatusNone,
454  /// Small yellow indicator, similar to iChat's idle image.
455  StatusPartiallyAvailable,
456  /// Small red indicator, similar to iChat's unavailable image.
457  StatusUnavailable,
458  /// A stop progress template image.
459  StopProgressFreestanding,
460  /// A stop progress button template image.
461  StopProgress,
462  /// An image of the empty trash can.
463  TrashEmpty,
464  /// An image of the full trash can.
465  TrashFull,
466  /// Permissions for a single user.
467  User,
468  /// User account toolbar icon for the preferences window.
469  UserAccounts,
470  /// Permissions for a group of users.
471  UserGroup,
472  /// Permissions for guests.
473  UserGuest,
474}
475
476impl From<NativeIcon> for muda::NativeIcon {
477  fn from(value: NativeIcon) -> Self {
478    match value {
479      NativeIcon::Add => muda::NativeIcon::Add,
480      NativeIcon::Advanced => muda::NativeIcon::Advanced,
481      NativeIcon::Bluetooth => muda::NativeIcon::Bluetooth,
482      NativeIcon::Bookmarks => muda::NativeIcon::Bookmarks,
483      NativeIcon::Caution => muda::NativeIcon::Caution,
484      NativeIcon::ColorPanel => muda::NativeIcon::ColorPanel,
485      NativeIcon::ColumnView => muda::NativeIcon::ColumnView,
486      NativeIcon::Computer => muda::NativeIcon::Computer,
487      NativeIcon::EnterFullScreen => muda::NativeIcon::EnterFullScreen,
488      NativeIcon::Everyone => muda::NativeIcon::Everyone,
489      NativeIcon::ExitFullScreen => muda::NativeIcon::ExitFullScreen,
490      NativeIcon::FlowView => muda::NativeIcon::FlowView,
491      NativeIcon::Folder => muda::NativeIcon::Folder,
492      NativeIcon::FolderBurnable => muda::NativeIcon::FolderBurnable,
493      NativeIcon::FolderSmart => muda::NativeIcon::FolderSmart,
494      NativeIcon::FollowLinkFreestanding => muda::NativeIcon::FollowLinkFreestanding,
495      NativeIcon::FontPanel => muda::NativeIcon::FontPanel,
496      NativeIcon::GoLeft => muda::NativeIcon::GoLeft,
497      NativeIcon::GoRight => muda::NativeIcon::GoRight,
498      NativeIcon::Home => muda::NativeIcon::Home,
499      NativeIcon::IChatTheater => muda::NativeIcon::IChatTheater,
500      NativeIcon::IconView => muda::NativeIcon::IconView,
501      NativeIcon::Info => muda::NativeIcon::Info,
502      NativeIcon::InvalidDataFreestanding => muda::NativeIcon::InvalidDataFreestanding,
503      NativeIcon::LeftFacingTriangle => muda::NativeIcon::LeftFacingTriangle,
504      NativeIcon::ListView => muda::NativeIcon::ListView,
505      NativeIcon::LockLocked => muda::NativeIcon::LockLocked,
506      NativeIcon::LockUnlocked => muda::NativeIcon::LockUnlocked,
507      NativeIcon::MenuMixedState => muda::NativeIcon::MenuMixedState,
508      NativeIcon::MenuOnState => muda::NativeIcon::MenuOnState,
509      NativeIcon::MobileMe => muda::NativeIcon::MobileMe,
510      NativeIcon::MultipleDocuments => muda::NativeIcon::MultipleDocuments,
511      NativeIcon::Network => muda::NativeIcon::Network,
512      NativeIcon::Path => muda::NativeIcon::Path,
513      NativeIcon::PreferencesGeneral => muda::NativeIcon::PreferencesGeneral,
514      NativeIcon::QuickLook => muda::NativeIcon::QuickLook,
515      NativeIcon::RefreshFreestanding => muda::NativeIcon::RefreshFreestanding,
516      NativeIcon::Refresh => muda::NativeIcon::Refresh,
517      NativeIcon::Remove => muda::NativeIcon::Remove,
518      NativeIcon::RevealFreestanding => muda::NativeIcon::RevealFreestanding,
519      NativeIcon::RightFacingTriangle => muda::NativeIcon::RightFacingTriangle,
520      NativeIcon::Share => muda::NativeIcon::Share,
521      NativeIcon::Slideshow => muda::NativeIcon::Slideshow,
522      NativeIcon::SmartBadge => muda::NativeIcon::SmartBadge,
523      NativeIcon::StatusAvailable => muda::NativeIcon::StatusAvailable,
524      NativeIcon::StatusNone => muda::NativeIcon::StatusNone,
525      NativeIcon::StatusPartiallyAvailable => muda::NativeIcon::StatusPartiallyAvailable,
526      NativeIcon::StatusUnavailable => muda::NativeIcon::StatusUnavailable,
527      NativeIcon::StopProgressFreestanding => muda::NativeIcon::StopProgressFreestanding,
528      NativeIcon::StopProgress => muda::NativeIcon::StopProgress,
529      NativeIcon::TrashEmpty => muda::NativeIcon::TrashEmpty,
530      NativeIcon::TrashFull => muda::NativeIcon::TrashFull,
531      NativeIcon::User => muda::NativeIcon::User,
532      NativeIcon::UserAccounts => muda::NativeIcon::UserAccounts,
533      NativeIcon::UserGroup => muda::NativeIcon::UserGroup,
534      NativeIcon::UserGuest => muda::NativeIcon::UserGuest,
535    }
536  }
537}
538
539/// An enumeration of all menu item kinds that could be added to
540/// a [`Menu`] or [`Submenu`]
541pub enum MenuItemKind<R: Runtime> {
542  /// Normal menu item
543  MenuItem(MenuItem<R>),
544  /// Submenu menu item
545  Submenu(Submenu<R>),
546  /// Predefined menu item
547  Predefined(PredefinedMenuItem<R>),
548  /// Check menu item
549  Check(CheckMenuItem<R>),
550  /// Icon menu item
551  Icon(IconMenuItem<R>),
552}
553
554impl<R: Runtime> MenuItemKind<R> {
555  /// Returns a unique identifier associated with this menu item.
556  pub fn id(&self) -> &MenuId {
557    match self {
558      MenuItemKind::MenuItem(i) => i.id(),
559      MenuItemKind::Submenu(i) => i.id(),
560      MenuItemKind::Predefined(i) => i.id(),
561      MenuItemKind::Check(i) => i.id(),
562      MenuItemKind::Icon(i) => i.id(),
563    }
564  }
565
566  pub(crate) fn inner(&self) -> &dyn IsMenuItem<R> {
567    match self {
568      MenuItemKind::MenuItem(i) => i,
569      MenuItemKind::Submenu(i) => i,
570      MenuItemKind::Predefined(i) => i,
571      MenuItemKind::Check(i) => i,
572      MenuItemKind::Icon(i) => i,
573    }
574  }
575
576  pub(crate) fn from_muda(app_handle: AppHandle<R>, i: muda::MenuItemKind) -> Self {
577    match i {
578      muda::MenuItemKind::MenuItem(i) => Self::MenuItem(MenuItem(Arc::new(MenuItemInner {
579        id: i.id().clone(),
580        inner: i.into(),
581        app_handle,
582      }))),
583      muda::MenuItemKind::Submenu(i) => Self::Submenu(Submenu(Arc::new(SubmenuInner {
584        id: i.id().clone(),
585        inner: i.into(),
586        app_handle,
587      }))),
588      muda::MenuItemKind::Predefined(i) => {
589        Self::Predefined(PredefinedMenuItem(Arc::new(PredefinedMenuItemInner {
590          id: i.id().clone(),
591          inner: i.into(),
592          app_handle,
593        })))
594      }
595      muda::MenuItemKind::Check(i) => Self::Check(CheckMenuItem(Arc::new(CheckMenuItemInner {
596        id: i.id().clone(),
597        inner: i.into(),
598        app_handle,
599      }))),
600      muda::MenuItemKind::Icon(i) => Self::Icon(IconMenuItem(Arc::new(IconMenuItemInner {
601        id: i.id().clone(),
602        inner: i.into(),
603        app_handle,
604      }))),
605    }
606  }
607
608  /// Casts this item to a [`MenuItem`], and returns `None` if it wasn't.
609  pub fn as_menuitem(&self) -> Option<&MenuItem<R>> {
610    match self {
611      MenuItemKind::MenuItem(i) => Some(i),
612      _ => None,
613    }
614  }
615
616  /// Casts this item to a [`MenuItem`], and panics if it wasn't.
617  pub fn as_menuitem_unchecked(&self) -> &MenuItem<R> {
618    match self {
619      MenuItemKind::MenuItem(i) => i,
620      _ => panic!("Not a MenuItem"),
621    }
622  }
623
624  /// Casts this item to a [`Submenu`], and returns `None` if it wasn't.
625  pub fn as_submenu(&self) -> Option<&Submenu<R>> {
626    match self {
627      MenuItemKind::Submenu(i) => Some(i),
628      _ => None,
629    }
630  }
631
632  /// Casts this item to a [`Submenu`], and panics if it wasn't.
633  pub fn as_submenu_unchecked(&self) -> &Submenu<R> {
634    match self {
635      MenuItemKind::Submenu(i) => i,
636      _ => panic!("Not a Submenu"),
637    }
638  }
639
640  /// Casts this item to a [`PredefinedMenuItem`], and returns `None` if it wasn't.
641  pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem<R>> {
642    match self {
643      MenuItemKind::Predefined(i) => Some(i),
644      _ => None,
645    }
646  }
647
648  /// Casts this item to a [`PredefinedMenuItem`], and panics if it wasn't.
649  pub fn as_predefined_menuitem_unchecked(&self) -> &PredefinedMenuItem<R> {
650    match self {
651      MenuItemKind::Predefined(i) => i,
652      _ => panic!("Not a PredefinedMenuItem"),
653    }
654  }
655
656  /// Casts this item to a [`CheckMenuItem`], and returns `None` if it wasn't.
657  pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem<R>> {
658    match self {
659      MenuItemKind::Check(i) => Some(i),
660      _ => None,
661    }
662  }
663
664  /// Casts this item to a [`CheckMenuItem`], and panics if it wasn't.
665  pub fn as_check_menuitem_unchecked(&self) -> &CheckMenuItem<R> {
666    match self {
667      MenuItemKind::Check(i) => i,
668      _ => panic!("Not a CheckMenuItem"),
669    }
670  }
671
672  /// Casts this item to a [`IconMenuItem`], and returns `None` if it wasn't.
673  pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem<R>> {
674    match self {
675      MenuItemKind::Icon(i) => Some(i),
676      _ => None,
677    }
678  }
679
680  /// Casts this item to a [`IconMenuItem`], and panics if it wasn't.
681  pub fn as_icon_menuitem_unchecked(&self) -> &IconMenuItem<R> {
682    match self {
683      MenuItemKind::Icon(i) => i,
684      _ => panic!("Not an IconMenuItem"),
685    }
686  }
687}
688
689impl<R: Runtime> Clone for MenuItemKind<R> {
690  fn clone(&self) -> Self {
691    match self {
692      Self::MenuItem(i) => Self::MenuItem(i.clone()),
693      Self::Submenu(i) => Self::Submenu(i.clone()),
694      Self::Predefined(i) => Self::Predefined(i.clone()),
695      Self::Check(i) => Self::Check(i.clone()),
696      Self::Icon(i) => Self::Icon(i.clone()),
697    }
698  }
699}
700
701impl<R: Runtime> sealed::IsMenuItemBase for MenuItemKind<R> {
702  fn inner_muda(&self) -> &dyn muda::IsMenuItem {
703    self.inner().inner_muda()
704  }
705}
706
707impl<R: Runtime> IsMenuItem<R> for MenuItemKind<R> {
708  fn kind(&self) -> MenuItemKind<R> {
709    self.clone()
710  }
711
712  fn id(&self) -> &MenuId {
713    self.id()
714  }
715}
716
717/// A trait that defines a generic item in a menu, which may be one of [`MenuItemKind`]
718///
719/// # Safety
720///
721/// This trait is ONLY meant to be implemented internally by the crate.
722pub trait IsMenuItem<R: Runtime>: sealed::IsMenuItemBase {
723  /// Returns the kind of this menu item.
724  fn kind(&self) -> MenuItemKind<R>;
725
726  /// Returns a unique identifier associated with this menu.
727  fn id(&self) -> &MenuId;
728}
729
730/// A helper trait with methods to help creating a context menu.
731///
732/// # Safety
733///
734/// This trait is ONLY meant to be implemented internally by the crate.
735pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {
736  /// Get the popup [`HMENU`] for this menu.
737  ///
738  /// The returned [`HMENU`] is valid as long as the [`ContextMenu`] is.
739  ///
740  /// [`HMENU`]: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types#HMENU
741  #[cfg(windows)]
742  #[cfg_attr(docsrs, doc(cfg(windows)))]
743  fn hpopupmenu(&self) -> crate::Result<isize>;
744
745  /// Popup this menu as a context menu on the specified window at the cursor position.
746  fn popup<R: crate::Runtime>(&self, window: crate::Window<R>) -> crate::Result<()>;
747
748  /// Popup this menu as a context menu on the specified window at the specified position.
749  ///
750  /// The position is relative to the window's top-left corner.
751  fn popup_at<R: crate::Runtime, P: Into<crate::Position>>(
752    &self,
753    window: crate::Window<R>,
754    position: P,
755  ) -> crate::Result<()>;
756}
757
758pub(crate) mod sealed {
759
760  pub trait IsMenuItemBase {
761    fn inner_muda(&self) -> &dyn muda::IsMenuItem;
762  }
763
764  pub trait ContextMenuBase {
765    fn inner_context(&self) -> &dyn muda::ContextMenu;
766    fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu>;
767    fn popup_inner<R: crate::Runtime, P: Into<crate::Position>>(
768      &self,
769      window: crate::Window<R>,
770      position: Option<P>,
771    ) -> crate::Result<()>;
772  }
773}
774
775#[cfg(windows)]
776pub(crate) fn map_to_menu_theme(theme: tauri_utils::Theme) -> muda::MenuTheme {
777  match theme {
778    tauri_utils::Theme::Light => muda::MenuTheme::Light,
779    tauri_utils::Theme::Dark => muda::MenuTheme::Dark,
780    _ => muda::MenuTheme::Auto,
781  }
782}