#![allow(clippy::uninlined_format_args)]
use crossbeam_channel::{unbounded, Receiver, Sender};
use std::sync::{LazyLock, OnceLock};
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))]
pub(crate) enum MenuItemType {
MenuItem,
Submenu,
Predefined,
Check,
Icon,
}
impl Default for MenuItemType {
fn default() -> Self {
Self::MenuItem
}
}
pub trait ContextMenu {
fn hpopupmenu(&self) -> isize;
unsafe fn show_context_menu_for_hwnd(
&self,
hwnd: isize,
position: Option<dpi::Position>,
) -> bool;
unsafe fn attach_menu_subclass_for_hwnd(&self, hwnd: isize);
unsafe fn detach_menu_subclass_from_hwnd(&self, hwnd: isize);
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MenuEvent {
pub id: MenuId,
}
pub type MenuEventReceiver = Receiver<MenuEvent>;
pub type MenuEventHandler = Box<dyn Fn(MenuEvent) + Send + Sync + 'static>;
static MENU_CHANNEL: LazyLock<(Sender<MenuEvent>, MenuEventReceiver)> = LazyLock::new(unbounded);
static MENU_EVENT_HANDLER: OnceLock<Option<MenuEventHandler>> = OnceLock::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>,
) -> Option<Option<MenuEventHandler>> {
if let Some(f) = f {
let boxed_handler = Box::new(f);
let _ = MENU_EVENT_HANDLER.set(Some(boxed_handler));
} else {
let _ = MENU_EVENT_HANDLER.set(None);
}
match MENU_EVENT_HANDLER.get() {
Some(Some(handler)) => Some(Some(Box::new(handler))),
_ => 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);
}
}
}