mod builders;
mod check;
mod icon;
#[allow(clippy::module_inception)]
mod menu;
mod normal;
pub(crate) mod plugin;
mod predefined;
mod submenu;
use std::sync::Arc;
pub use builders::*;
pub use menu::{HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
use serde::{Deserialize, Serialize};
use crate::{image::Image, AppHandle, Runtime};
pub use muda::MenuId;
macro_rules! run_item_main_thread {
($self:ident, $ex:expr) => {{
use std::sync::mpsc::channel;
let (tx, rx) = channel();
let self_ = $self.clone();
let task = move || {
let f = $ex;
let _ = tx.send(f(self_));
};
$self
.app_handle()
.run_on_main_thread(task)
.and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
}};
}
pub(crate) use run_item_main_thread;
#[derive(Debug, Clone, Serialize)]
pub struct MenuEvent {
pub id: MenuId,
}
impl MenuEvent {
pub fn id(&self) -> &MenuId {
&self.id
}
}
impl From<muda::MenuEvent> for MenuEvent {
fn from(value: muda::MenuEvent) -> Self {
Self { id: value.id }
}
}
macro_rules! gen_wrappers {
(
$(
$(#[$attr:meta])*
$type:ident($inner:ident$(, $kind:ident)?)
),*
) => {
$(
#[tauri_macros::default_runtime(crate::Wry, wry)]
pub(crate) struct $inner<R: $crate::Runtime> {
id: $crate::menu::MenuId,
inner: ::std::option::Option<::muda::$type>,
app_handle: $crate::AppHandle<R>,
}
unsafe impl<R: $crate::Runtime> Sync for $inner<R> {}
unsafe impl<R: $crate::Runtime> Send for $inner<R> {}
impl<R: Runtime> $crate::Resource for $type<R> {}
impl<R: $crate::Runtime> Clone for $inner<R> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
inner: self.inner.clone(),
app_handle: self.app_handle.clone(),
}
}
}
impl<R: Runtime> Drop for $inner<R> {
fn drop(&mut self) {
let inner = self.inner.take();
let inner = $crate::UnsafeSend(inner);
let _ = self.app_handle.run_on_main_thread(move || {
drop(inner.take());
});
}
}
impl<R: Runtime> AsRef<::muda::$type> for $inner<R> {
fn as_ref(&self) -> &::muda::$type {
self.inner.as_ref().unwrap()
}
}
$(#[$attr])*
pub struct $type<R: $crate::Runtime>(::std::sync::Arc<$inner<R>>);
impl<R: $crate::Runtime> Clone for $type<R> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
$(
impl<R: $crate::Runtime> $crate::menu::sealed::IsMenuItemBase for $type<R> {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
(*self.0).as_ref()
}
}
impl<R: $crate::Runtime> $crate::menu::IsMenuItem<R> for $type<R> {
fn kind(&self) -> MenuItemKind<R> {
MenuItemKind::$kind(self.clone())
}
fn id(&self) -> &MenuId {
&self.0.id
}
}
)*
)*
};
}
gen_wrappers!(
Menu(MenuInner),
MenuItem(MenuItemInner, MenuItem),
Submenu(SubmenuInner, Submenu),
PredefinedMenuItem(PredefinedMenuItemInner, Predefined),
CheckMenuItem(CheckMenuItemInner, Check),
IconMenuItem(IconMenuItemInner, Icon)
);
#[derive(Debug, Clone, Default)]
pub struct AboutMetadata<'a> {
pub name: Option<String>,
pub version: Option<String>,
pub short_version: Option<String>,
pub authors: Option<Vec<String>>,
pub comments: Option<String>,
pub copyright: Option<String>,
pub license: Option<String>,
pub website: Option<String>,
pub website_label: Option<String>,
pub credits: Option<String>,
pub icon: Option<Image<'a>>,
}
#[derive(Clone, Debug, Default)]
pub struct AboutMetadataBuilder<'a>(AboutMetadata<'a>);
impl<'a> AboutMetadataBuilder<'a> {
pub fn new() -> Self {
Default::default()
}
pub fn name<S: Into<String>>(mut self, name: Option<S>) -> Self {
self.0.name = name.map(|s| s.into());
self
}
pub fn version<S: Into<String>>(mut self, version: Option<S>) -> Self {
self.0.version = version.map(|s| s.into());
self
}
pub fn short_version<S: Into<String>>(mut self, short_version: Option<S>) -> Self {
self.0.short_version = short_version.map(|s| s.into());
self
}
pub fn authors(mut self, authors: Option<Vec<String>>) -> Self {
self.0.authors = authors;
self
}
pub fn comments<S: Into<String>>(mut self, comments: Option<S>) -> Self {
self.0.comments = comments.map(|s| s.into());
self
}
pub fn copyright<S: Into<String>>(mut self, copyright: Option<S>) -> Self {
self.0.copyright = copyright.map(|s| s.into());
self
}
pub fn license<S: Into<String>>(mut self, license: Option<S>) -> Self {
self.0.license = license.map(|s| s.into());
self
}
pub fn website<S: Into<String>>(mut self, website: Option<S>) -> Self {
self.0.website = website.map(|s| s.into());
self
}
pub fn website_label<S: Into<String>>(mut self, website_label: Option<S>) -> Self {
self.0.website_label = website_label.map(|s| s.into());
self
}
pub fn credits<S: Into<String>>(mut self, credits: Option<S>) -> Self {
self.0.credits = credits.map(|s| s.into());
self
}
pub fn icon(mut self, icon: Option<Image<'a>>) -> Self {
self.0.icon = icon;
self
}
pub fn build(self) -> AboutMetadata<'a> {
self.0
}
}
impl TryFrom<AboutMetadata<'_>> for muda::AboutMetadata {
type Error = crate::Error;
fn try_from(value: AboutMetadata<'_>) -> Result<Self, Self::Error> {
let icon = match value.icon {
Some(i) => Some(i.try_into()?),
None => None,
};
Ok(Self {
authors: value.authors,
name: value.name,
version: value.version,
short_version: value.short_version,
comments: value.comments,
copyright: value.copyright,
license: value.license,
website: value.website,
website_label: value.website_label,
credits: value.credits,
icon,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
pub enum NativeIcon {
Add,
Advanced,
Bluetooth,
Bookmarks,
Caution,
ColorPanel,
ColumnView,
Computer,
EnterFullScreen,
Everyone,
ExitFullScreen,
FlowView,
Folder,
FolderBurnable,
FolderSmart,
FollowLinkFreestanding,
FontPanel,
GoLeft,
GoRight,
Home,
IChatTheater,
IconView,
Info,
InvalidDataFreestanding,
LeftFacingTriangle,
ListView,
LockLocked,
LockUnlocked,
MenuMixedState,
MenuOnState,
MobileMe,
MultipleDocuments,
Network,
Path,
PreferencesGeneral,
QuickLook,
RefreshFreestanding,
Refresh,
Remove,
RevealFreestanding,
RightFacingTriangle,
Share,
Slideshow,
SmartBadge,
StatusAvailable,
StatusNone,
StatusPartiallyAvailable,
StatusUnavailable,
StopProgressFreestanding,
StopProgress,
TrashEmpty,
TrashFull,
User,
UserAccounts,
UserGroup,
UserGuest,
}
impl From<NativeIcon> for muda::NativeIcon {
fn from(value: NativeIcon) -> Self {
match value {
NativeIcon::Add => muda::NativeIcon::Add,
NativeIcon::Advanced => muda::NativeIcon::Advanced,
NativeIcon::Bluetooth => muda::NativeIcon::Bluetooth,
NativeIcon::Bookmarks => muda::NativeIcon::Bookmarks,
NativeIcon::Caution => muda::NativeIcon::Caution,
NativeIcon::ColorPanel => muda::NativeIcon::ColorPanel,
NativeIcon::ColumnView => muda::NativeIcon::ColumnView,
NativeIcon::Computer => muda::NativeIcon::Computer,
NativeIcon::EnterFullScreen => muda::NativeIcon::EnterFullScreen,
NativeIcon::Everyone => muda::NativeIcon::Everyone,
NativeIcon::ExitFullScreen => muda::NativeIcon::ExitFullScreen,
NativeIcon::FlowView => muda::NativeIcon::FlowView,
NativeIcon::Folder => muda::NativeIcon::Folder,
NativeIcon::FolderBurnable => muda::NativeIcon::FolderBurnable,
NativeIcon::FolderSmart => muda::NativeIcon::FolderSmart,
NativeIcon::FollowLinkFreestanding => muda::NativeIcon::FollowLinkFreestanding,
NativeIcon::FontPanel => muda::NativeIcon::FontPanel,
NativeIcon::GoLeft => muda::NativeIcon::GoLeft,
NativeIcon::GoRight => muda::NativeIcon::GoRight,
NativeIcon::Home => muda::NativeIcon::Home,
NativeIcon::IChatTheater => muda::NativeIcon::IChatTheater,
NativeIcon::IconView => muda::NativeIcon::IconView,
NativeIcon::Info => muda::NativeIcon::Info,
NativeIcon::InvalidDataFreestanding => muda::NativeIcon::InvalidDataFreestanding,
NativeIcon::LeftFacingTriangle => muda::NativeIcon::LeftFacingTriangle,
NativeIcon::ListView => muda::NativeIcon::ListView,
NativeIcon::LockLocked => muda::NativeIcon::LockLocked,
NativeIcon::LockUnlocked => muda::NativeIcon::LockUnlocked,
NativeIcon::MenuMixedState => muda::NativeIcon::MenuMixedState,
NativeIcon::MenuOnState => muda::NativeIcon::MenuOnState,
NativeIcon::MobileMe => muda::NativeIcon::MobileMe,
NativeIcon::MultipleDocuments => muda::NativeIcon::MultipleDocuments,
NativeIcon::Network => muda::NativeIcon::Network,
NativeIcon::Path => muda::NativeIcon::Path,
NativeIcon::PreferencesGeneral => muda::NativeIcon::PreferencesGeneral,
NativeIcon::QuickLook => muda::NativeIcon::QuickLook,
NativeIcon::RefreshFreestanding => muda::NativeIcon::RefreshFreestanding,
NativeIcon::Refresh => muda::NativeIcon::Refresh,
NativeIcon::Remove => muda::NativeIcon::Remove,
NativeIcon::RevealFreestanding => muda::NativeIcon::RevealFreestanding,
NativeIcon::RightFacingTriangle => muda::NativeIcon::RightFacingTriangle,
NativeIcon::Share => muda::NativeIcon::Share,
NativeIcon::Slideshow => muda::NativeIcon::Slideshow,
NativeIcon::SmartBadge => muda::NativeIcon::SmartBadge,
NativeIcon::StatusAvailable => muda::NativeIcon::StatusAvailable,
NativeIcon::StatusNone => muda::NativeIcon::StatusNone,
NativeIcon::StatusPartiallyAvailable => muda::NativeIcon::StatusPartiallyAvailable,
NativeIcon::StatusUnavailable => muda::NativeIcon::StatusUnavailable,
NativeIcon::StopProgressFreestanding => muda::NativeIcon::StopProgressFreestanding,
NativeIcon::StopProgress => muda::NativeIcon::StopProgress,
NativeIcon::TrashEmpty => muda::NativeIcon::TrashEmpty,
NativeIcon::TrashFull => muda::NativeIcon::TrashFull,
NativeIcon::User => muda::NativeIcon::User,
NativeIcon::UserAccounts => muda::NativeIcon::UserAccounts,
NativeIcon::UserGroup => muda::NativeIcon::UserGroup,
NativeIcon::UserGuest => muda::NativeIcon::UserGuest,
}
}
}
pub enum MenuItemKind<R: Runtime> {
MenuItem(MenuItem<R>),
Submenu(Submenu<R>),
Predefined(PredefinedMenuItem<R>),
Check(CheckMenuItem<R>),
Icon(IconMenuItem<R>),
}
impl<R: Runtime> MenuItemKind<R> {
pub fn id(&self) -> &MenuId {
match self {
MenuItemKind::MenuItem(i) => i.id(),
MenuItemKind::Submenu(i) => i.id(),
MenuItemKind::Predefined(i) => i.id(),
MenuItemKind::Check(i) => i.id(),
MenuItemKind::Icon(i) => i.id(),
}
}
pub(crate) fn inner(&self) -> &dyn IsMenuItem<R> {
match self {
MenuItemKind::MenuItem(i) => i,
MenuItemKind::Submenu(i) => i,
MenuItemKind::Predefined(i) => i,
MenuItemKind::Check(i) => i,
MenuItemKind::Icon(i) => i,
}
}
pub(crate) fn from_muda(app_handle: AppHandle<R>, i: muda::MenuItemKind) -> Self {
match i {
muda::MenuItemKind::MenuItem(i) => Self::MenuItem(MenuItem(Arc::new(MenuItemInner {
id: i.id().clone(),
inner: i.into(),
app_handle,
}))),
muda::MenuItemKind::Submenu(i) => Self::Submenu(Submenu(Arc::new(SubmenuInner {
id: i.id().clone(),
inner: i.into(),
app_handle,
}))),
muda::MenuItemKind::Predefined(i) => {
Self::Predefined(PredefinedMenuItem(Arc::new(PredefinedMenuItemInner {
id: i.id().clone(),
inner: i.into(),
app_handle,
})))
}
muda::MenuItemKind::Check(i) => Self::Check(CheckMenuItem(Arc::new(CheckMenuItemInner {
id: i.id().clone(),
inner: i.into(),
app_handle,
}))),
muda::MenuItemKind::Icon(i) => Self::Icon(IconMenuItem(Arc::new(IconMenuItemInner {
id: i.id().clone(),
inner: i.into(),
app_handle,
}))),
}
}
pub fn as_menuitem(&self) -> Option<&MenuItem<R>> {
match self {
MenuItemKind::MenuItem(i) => Some(i),
_ => None,
}
}
pub fn as_menuitem_unchecked(&self) -> &MenuItem<R> {
match self {
MenuItemKind::MenuItem(i) => i,
_ => panic!("Not a MenuItem"),
}
}
pub fn as_submenu(&self) -> Option<&Submenu<R>> {
match self {
MenuItemKind::Submenu(i) => Some(i),
_ => None,
}
}
pub fn as_submenu_unchecked(&self) -> &Submenu<R> {
match self {
MenuItemKind::Submenu(i) => i,
_ => panic!("Not a Submenu"),
}
}
pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem<R>> {
match self {
MenuItemKind::Predefined(i) => Some(i),
_ => None,
}
}
pub fn as_predefined_menuitem_unchecked(&self) -> &PredefinedMenuItem<R> {
match self {
MenuItemKind::Predefined(i) => i,
_ => panic!("Not a PredefinedMenuItem"),
}
}
pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem<R>> {
match self {
MenuItemKind::Check(i) => Some(i),
_ => None,
}
}
pub fn as_check_menuitem_unchecked(&self) -> &CheckMenuItem<R> {
match self {
MenuItemKind::Check(i) => i,
_ => panic!("Not a CheckMenuItem"),
}
}
pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem<R>> {
match self {
MenuItemKind::Icon(i) => Some(i),
_ => None,
}
}
pub fn as_icon_menuitem_unchecked(&self) -> &IconMenuItem<R> {
match self {
MenuItemKind::Icon(i) => i,
_ => panic!("Not an IconMenuItem"),
}
}
}
impl<R: Runtime> Clone for MenuItemKind<R> {
fn clone(&self) -> Self {
match self {
Self::MenuItem(i) => Self::MenuItem(i.clone()),
Self::Submenu(i) => Self::Submenu(i.clone()),
Self::Predefined(i) => Self::Predefined(i.clone()),
Self::Check(i) => Self::Check(i.clone()),
Self::Icon(i) => Self::Icon(i.clone()),
}
}
}
impl<R: Runtime> sealed::IsMenuItemBase for MenuItemKind<R> {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
self.inner().inner_muda()
}
}
impl<R: Runtime> IsMenuItem<R> for MenuItemKind<R> {
fn kind(&self) -> MenuItemKind<R> {
self.clone()
}
fn id(&self) -> &MenuId {
self.id()
}
}
pub trait IsMenuItem<R: Runtime>: sealed::IsMenuItemBase {
fn kind(&self) -> MenuItemKind<R>;
fn id(&self) -> &MenuId;
}
pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {
#[cfg(windows)]
#[cfg_attr(docsrs, doc(cfg(windows)))]
fn hpopupmenu(&self) -> crate::Result<isize>;
fn popup<R: crate::Runtime>(&self, window: crate::Window<R>) -> crate::Result<()>;
fn popup_at<R: crate::Runtime, P: Into<crate::Position>>(
&self,
window: crate::Window<R>,
position: P,
) -> crate::Result<()>;
}
pub(crate) mod sealed {
pub trait IsMenuItemBase {
fn inner_muda(&self) -> &dyn muda::IsMenuItem;
}
pub trait ContextMenuBase {
fn inner_context(&self) -> &dyn muda::ContextMenu;
fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu>;
fn popup_inner<R: crate::Runtime, P: Into<crate::Position>>(
&self,
window: crate::Window<R>,
position: Option<P>,
) -> crate::Result<()>;
}
}
#[cfg(windows)]
pub(crate) fn map_to_menu_theme(theme: tauri_utils::Theme) -> muda::MenuTheme {
match theme {
tauri_utils::Theme::Light => muda::MenuTheme::Light,
tauri_utils::Theme::Dark => muda::MenuTheme::Dark,
_ => muda::MenuTheme::Auto,
}
}