#![allow(clippy::uninlined_format_args)]
use crossbeam_channel::{unbounded, Receiver, Sender};
use once_cell::sync::{Lazy, OnceCell};
pub mod about_metadata;
pub mod accelerator;
mod builders;
mod error;
mod icon;
mod items;
mod menu;
mod menu_id;
mod platform_impl;
mod util;
pub use about_metadata::AboutMetadata;
pub use builders::*;
pub use dpi;
pub use error::*;
pub use icon::{BadIcon, Icon, NativeIcon};
pub use items::*;
pub use menu::*;
pub use menu_id::MenuId;
#[derive(Clone)]
pub enum MenuItemKind {
MenuItem(MenuItem),
Submenu(Submenu),
Predefined(PredefinedMenuItem),
Check(CheckMenuItem),
Icon(IconMenuItem),
}
impl MenuItemKind {
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 fn as_menuitem(&self) -> Option<&MenuItem> {
match self {
MenuItemKind::MenuItem(i) => Some(i),
_ => None,
}
}
pub fn as_menuitem_unchecked(&self) -> &MenuItem {
match self {
MenuItemKind::MenuItem(i) => i,
_ => panic!("Not a MenuItem"),
}
}
pub fn as_submenu(&self) -> Option<&Submenu> {
match self {
MenuItemKind::Submenu(i) => Some(i),
_ => None,
}
}
pub fn as_submenu_unchecked(&self) -> &Submenu {
match self {
MenuItemKind::Submenu(i) => i,
_ => panic!("Not a Submenu"),
}
}
pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem> {
match self {
MenuItemKind::Predefined(i) => Some(i),
_ => None,
}
}
pub fn as_predefined_menuitem_unchecked(&self) -> &PredefinedMenuItem {
match self {
MenuItemKind::Predefined(i) => i,
_ => panic!("Not a PredefinedMenuItem"),
}
}
pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem> {
match self {
MenuItemKind::Check(i) => Some(i),
_ => None,
}
}
pub fn as_check_menuitem_unchecked(&self) -> &CheckMenuItem {
match self {
MenuItemKind::Check(i) => i,
_ => panic!("Not a CheckMenuItem"),
}
}
pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem> {
match self {
MenuItemKind::Icon(i) => Some(i),
_ => None,
}
}
pub fn as_icon_menuitem_unchecked(&self) -> &IconMenuItem {
match self {
MenuItemKind::Icon(i) => i,
_ => panic!("Not an IconMenuItem"),
}
}
pub fn into_id(self) -> MenuId {
match self {
MenuItemKind::MenuItem(i) => i.into_id(),
MenuItemKind::Submenu(i) => i.into_id(),
MenuItemKind::Predefined(i) => i.into_id(),
MenuItemKind::Check(i) => i.into_id(),
MenuItemKind::Icon(i) => i.into_id(),
}
}
}
pub trait IsMenuItem: sealed::IsMenuItemBase {
fn kind(&self) -> MenuItemKind;
fn id(&self) -> &MenuId;
fn into_id(self) -> MenuId;
}
mod sealed {
pub trait IsMenuItemBase {}
}
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Default)]
pub(crate) enum MenuItemType {
#[default]
MenuItem,
Submenu,
Predefined,
Check,
Icon,
}
pub trait ContextMenu {
#[cfg(target_os = "windows")]
fn hpopupmenu(&self) -> isize;
#[cfg(target_os = "windows")]
unsafe fn show_context_menu_for_hwnd(
&self,
hwnd: isize,
position: Option<dpi::Position>,
) -> bool;
#[cfg(target_os = "windows")]
unsafe fn attach_menu_subclass_for_hwnd(&self, hwnd: isize);
#[cfg(target_os = "windows")]
unsafe fn detach_menu_subclass_from_hwnd(&self, hwnd: isize);
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "gtk"
))]
fn show_context_menu_for_gtk_window(
&self,
w: >k::Window,
position: Option<dpi::Position>,
) -> bool;
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "gtk"
))]
fn gtk_context_menu(&self) -> gtk::Menu;
#[cfg(target_os = "macos")]
unsafe fn show_context_menu_for_nsview(
&self,
view: *const std::ffi::c_void,
position: Option<dpi::Position>,
) -> bool;
#[cfg(target_os = "macos")]
fn ns_menu(&self) -> *mut std::ffi::c_void;
fn as_menu(&self) -> Option<&Menu> {
None
}
fn as_menu_unchecked(&self) -> &Menu {
self.as_menu().expect("Not a Menu")
}
fn as_submenu(&self) -> Option<&Submenu> {
None
}
fn as_submenu_unchecked(&self) -> &Menu {
self.as_menu().expect("Not a Submenu")
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MenuEvent {
pub id: MenuId,
}
pub type MenuEventReceiver = Receiver<MenuEvent>;
type MenuEventHandler = Box<dyn Fn(MenuEvent) + Send + Sync + 'static>;
static MENU_CHANNEL: Lazy<(Sender<MenuEvent>, MenuEventReceiver)> = Lazy::new(unbounded);
static MENU_EVENT_HANDLER: OnceCell<Option<MenuEventHandler>> = OnceCell::new();
impl MenuEvent {
pub fn id(&self) -> &MenuId {
&self.id
}
pub fn receiver<'a>() -> &'a MenuEventReceiver {
&MENU_CHANNEL.1
}
pub fn set_event_handler<F: Fn(MenuEvent) + Send + Sync + 'static>(f: Option<F>) {
if let Some(f) = f {
let _ = MENU_EVENT_HANDLER.set(Some(Box::new(f)));
} else {
let _ = MENU_EVENT_HANDLER.set(None);
}
}
pub(crate) fn send(event: MenuEvent) {
if let Some(handler) = MENU_EVENT_HANDLER.get_or_init(|| None) {
handler(event);
} else {
let _ = MENU_CHANNEL.0.send(event);
}
}
}