extern crate alloc;
use alloc::vec::Vec;
use core::hash::Hash;
use azul_css::AzString;
use crate::{
callbacks::{CoreCallback, CoreCallbackType},
refany::RefAny,
resources::ImageRef,
window::{
ContextMenuMouseButton, KeyboardState, OptionVirtualKeyCodeCombo, VirtualKeyCode,
VirtualKeyCodeCombo,
},
};
#[must_use]
pub fn accelerator_matches(
combo: &VirtualKeyCodeCombo,
keyboard: &KeyboardState,
pressed: VirtualKeyCode,
) -> bool {
let mut want_ctrl = false;
let mut want_shift = false;
let mut want_alt = false;
let mut want_primary = false;
let mut main_key: Option<VirtualKeyCode> = None;
for key in combo.keys.as_ref() {
match key {
VirtualKeyCode::LControl | VirtualKeyCode::RControl => want_ctrl = true,
VirtualKeyCode::LShift | VirtualKeyCode::RShift => want_shift = true,
VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => want_alt = true,
VirtualKeyCode::LWin | VirtualKeyCode::RWin => want_primary = true,
other => {
if main_key.is_some() {
return false;
}
main_key = Some(*other);
}
}
}
if main_key != Some(pressed) {
return false;
}
if want_shift != keyboard.shift_down() || want_alt != keyboard.alt_down() {
return false;
}
if cfg!(target_os = "macos") {
want_ctrl == keyboard.ctrl_down() && want_primary == keyboard.super_down()
} else {
(want_ctrl || want_primary) == keyboard.ctrl_down() && !keyboard.super_down()
}
}
fn find_accelerated_in<'a>(
items: &'a [MenuItem],
keyboard: &KeyboardState,
pressed: VirtualKeyCode,
) -> Option<&'a StringMenuItem> {
for item in items {
let MenuItem::String(s) = item else {
continue;
};
if s.menu_item_state == MenuItemState::Normal {
if let OptionVirtualKeyCodeCombo::Some(combo) = &s.accelerator {
if accelerator_matches(combo, keyboard, pressed) {
return Some(s);
}
}
}
if let Some(found) = find_accelerated_in(s.children.as_ref(), keyboard, pressed) {
return Some(found);
}
}
None
}
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C)]
pub struct Menu {
pub items: MenuItemVec,
pub position: MenuPopupPosition,
pub context_mouse_btn: ContextMenuMouseButton,
}
impl_option!(
Menu,
OptionMenu,
copy = false,
[Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
);
impl Menu {
#[must_use]
pub fn find_accelerated_item<'a>(
&'a self,
keyboard: &KeyboardState,
pressed: VirtualKeyCode,
) -> Option<&'a StringMenuItem> {
find_accelerated_in(self.items.as_ref(), keyboard, pressed)
}
#[must_use]
pub const fn create(items: MenuItemVec) -> Self {
Self {
items,
position: MenuPopupPosition::AutoCursor,
context_mouse_btn: ContextMenuMouseButton::Right,
}
}
#[must_use]
pub const fn with_position(mut self, position: MenuPopupPosition) -> Self {
self.position = position;
self
}
#[must_use]
pub fn get_hash(&self) -> u64 {
use core::hash::Hasher;
let mut hasher = crate::hash::DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C)]
pub enum MenuPopupPosition {
BottomLeftOfCursor,
BottomRightOfCursor,
TopLeftOfCursor,
TopRightOfCursor,
BottomOfHitRect,
LeftOfHitRect,
TopOfHitRect,
RightOfHitRect,
AutoCursor,
AutoHitRect,
}
impl Default for MenuPopupPosition {
fn default() -> Self {
Self::AutoCursor
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C)]
pub enum MenuItemState {
Normal,
Greyed,
Disabled,
}
#[allow(variant_size_differences)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C, u8)]
#[allow(clippy::large_enum_variant)] pub enum MenuItem {
String(StringMenuItem),
Separator,
BreakLine,
}
impl_option!(
MenuItem,
OptionMenuItem,
copy = false,
[Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
);
impl_vec!(
MenuItem,
MenuItemVec,
MenuItemVecDestructor,
MenuItemVecDestructorType,
MenuItemVecSlice,
OptionMenuItem
);
impl_vec_clone!(MenuItem, MenuItemVec, MenuItemVecDestructor);
impl_vec_debug!(MenuItem, MenuItemVec);
impl_vec_partialeq!(MenuItem, MenuItemVec);
impl_vec_partialord!(MenuItem, MenuItemVec);
impl_vec_hash!(MenuItem, MenuItemVec);
impl_vec_eq!(MenuItem, MenuItemVec);
impl_vec_ord!(MenuItem, MenuItemVec);
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C)]
pub struct StringMenuItem {
pub label: AzString,
pub accelerator: OptionVirtualKeyCodeCombo,
pub callback: OptionCoreMenuCallback,
pub menu_item_state: MenuItemState,
pub icon: OptionMenuItemIcon,
pub children: MenuItemVec,
}
impl StringMenuItem {
#[must_use]
pub const fn create(label: AzString) -> Self {
Self {
label,
accelerator: OptionVirtualKeyCodeCombo::None,
callback: OptionCoreMenuCallback::None,
menu_item_state: MenuItemState::Normal,
icon: OptionMenuItemIcon::None,
children: MenuItemVec::from_const_slice(&[]),
}
}
#[must_use]
pub fn with_children(mut self, children: MenuItemVec) -> Self {
self.children = children;
self
}
#[must_use]
pub fn with_child(mut self, child: MenuItem) -> Self {
let mut children = self.children.into_library_owned_vec();
children.push(child);
self.children = children.into();
self
}
#[must_use]
pub fn with_callback<I: Into<CoreCallback>>(mut self, data: RefAny, callback: I) -> Self {
self.callback = Some(CoreMenuCallback {
refany: data,
callback: callback.into(),
})
.into();
self
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C, u8)]
pub enum MenuItemIcon {
Checkbox(bool),
Image(ImageRef),
}
impl_option!(
MenuItemIcon,
OptionMenuItemIcon,
copy = false,
[Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
);
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[repr(C)]
pub struct CoreMenuCallback {
pub refany: RefAny,
pub callback: CoreCallback,
}
impl_option!(
CoreMenuCallback,
OptionCoreMenuCallback,
copy = false,
[Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
);
#[cfg(test)]
#[path = "menu_test.rs"]
mod menu_test;