1mod 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#[derive(Debug, Clone, Serialize)]
45pub struct MenuEvent {
46 pub id: MenuId,
48}
49
50impl MenuEvent {
51 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 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 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 Menu(MenuInner),
152 MenuItem(MenuItemInner, MenuItem),
154 Submenu(SubmenuInner, Submenu),
156 PredefinedMenuItem(PredefinedMenuItemInner, Predefined),
158 CheckMenuItem(CheckMenuItemInner, Check),
162 IconMenuItem(IconMenuItemInner, Icon)
165);
166
167#[derive(Debug, Clone, Default)]
169pub struct AboutMetadata<'a> {
170 pub name: Option<String>,
172 pub version: Option<String>,
174 pub short_version: Option<String>,
180 pub authors: Option<Vec<String>>,
186 pub comments: Option<String>,
192 pub copyright: Option<String>,
194 pub license: Option<String>,
200 pub website: Option<String>,
206 pub website_label: Option<String>,
212 pub credits: Option<String>,
218 pub icon: Option<Image<'a>>,
224}
225
226#[derive(Clone, Debug, Default)]
228pub struct AboutMetadataBuilder<'a>(AboutMetadata<'a>);
229
230impl<'a> AboutMetadataBuilder<'a> {
231 pub fn new() -> Self {
233 Default::default()
234 }
235
236 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 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 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 pub fn authors(mut self, authors: Option<Vec<String>>) -> Self {
261 self.0.authors = authors;
262 self
263 }
264 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 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 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 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 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 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 pub fn icon(mut self, icon: Option<Image<'a>>) -> Self {
320 self.0.icon = icon;
321 self
322 }
323
324 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
361pub enum NativeIcon {
362 Add,
364 Advanced,
366 Bluetooth,
368 Bookmarks,
370 Caution,
372 ColorPanel,
374 ColumnView,
376 Computer,
378 EnterFullScreen,
380 Everyone,
382 ExitFullScreen,
384 FlowView,
386 Folder,
388 FolderBurnable,
390 FolderSmart,
392 FollowLinkFreestanding,
394 FontPanel,
396 GoLeft,
398 GoRight,
400 Home,
402 IChatTheater,
404 IconView,
406 Info,
408 InvalidDataFreestanding,
410 LeftFacingTriangle,
412 ListView,
414 LockLocked,
416 LockUnlocked,
418 MenuMixedState,
420 MenuOnState,
422 MobileMe,
424 MultipleDocuments,
426 Network,
428 Path,
430 PreferencesGeneral,
432 QuickLook,
434 RefreshFreestanding,
436 Refresh,
438 Remove,
440 RevealFreestanding,
442 RightFacingTriangle,
444 Share,
446 Slideshow,
448 SmartBadge,
450 StatusAvailable,
452 StatusNone,
454 StatusPartiallyAvailable,
456 StatusUnavailable,
458 StopProgressFreestanding,
460 StopProgress,
462 TrashEmpty,
464 TrashFull,
466 User,
468 UserAccounts,
470 UserGroup,
472 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
539pub enum MenuItemKind<R: Runtime> {
542 MenuItem(MenuItem<R>),
544 Submenu(Submenu<R>),
546 Predefined(PredefinedMenuItem<R>),
548 Check(CheckMenuItem<R>),
550 Icon(IconMenuItem<R>),
552}
553
554impl<R: Runtime> MenuItemKind<R> {
555 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 pub fn as_menuitem(&self) -> Option<&MenuItem<R>> {
610 match self {
611 MenuItemKind::MenuItem(i) => Some(i),
612 _ => None,
613 }
614 }
615
616 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 pub fn as_submenu(&self) -> Option<&Submenu<R>> {
626 match self {
627 MenuItemKind::Submenu(i) => Some(i),
628 _ => None,
629 }
630 }
631
632 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 pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem<R>> {
642 match self {
643 MenuItemKind::Predefined(i) => Some(i),
644 _ => None,
645 }
646 }
647
648 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 pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem<R>> {
658 match self {
659 MenuItemKind::Check(i) => Some(i),
660 _ => None,
661 }
662 }
663
664 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 pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem<R>> {
674 match self {
675 MenuItemKind::Icon(i) => Some(i),
676 _ => None,
677 }
678 }
679
680 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
717pub trait IsMenuItem<R: Runtime>: sealed::IsMenuItemBase {
723 fn kind(&self) -> MenuItemKind<R>;
725
726 fn id(&self) -> &MenuId;
728}
729
730pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {
736 #[cfg(windows)]
742 #[cfg_attr(docsrs, doc(cfg(windows)))]
743 fn hpopupmenu(&self) -> crate::Result<isize>;
744
745 fn popup<R: crate::Runtime>(&self, window: crate::Window<R>) -> crate::Result<()>;
747
748 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}