use std::io;
use windows::Win32::UI::WindowsAndMessaging::{
AppendMenuW, CreateMenu, DestroyMenu, DrawMenuBar, HMENU, MF_POPUP, MF_STRING, SetMenu,
};
use windows::core::PCWSTR;
use super::config::windows_error;
use super::native::NativeWindow;
use super::popup_menu::{MAX_POPUP_MENU_ITEMS, OwnedMenu, PopupMenu, encode_label, invalid};
pub const MAX_MENU_BAR_MENUS: usize = 16;
pub const MAX_PENDING_MENU_COMMANDS: usize = 64;
const COMMAND_STRIDE: usize = 256;
const _: () = assert!(MAX_POPUP_MENU_ITEMS < COMMAND_STRIDE);
const _: () = assert!((MAX_MENU_BAR_MENUS + 1) * COMMAND_STRIDE <= u16::MAX as usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MenuCommand {
pub menu: usize,
pub item: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MenuBar {
menus: Vec<(Vec<u16>, PopupMenu)>,
}
impl MenuBar {
pub fn new<S: AsRef<str>>(menus: Vec<(S, PopupMenu)>) -> io::Result<Self> {
if menus.is_empty() || menus.len() > MAX_MENU_BAR_MENUS {
return Err(invalid("a menu bar holds 1 to 16 menus"));
}
let menus = menus
.into_iter()
.map(|(title, menu)| Ok((encode_label(title.as_ref())?, menu)))
.collect::<io::Result<_>>()?;
Ok(Self { menus })
}
fn build(&self) -> io::Result<OwnedMenu> {
let bar = OwnedMenu(unsafe { CreateMenu() }.map_err(windows_error)?);
for (index, (title, menu)) in self.menus.iter().enumerate() {
let submenu = menu.build((index + 1) * COMMAND_STRIDE)?;
unsafe {
AppendMenuW(
bar.0,
MF_POPUP | MF_STRING,
submenu.0.0 as usize,
PCWSTR(title.as_ptr()),
)
}
.map_err(windows_error)?;
let _ = submenu.into_raw();
}
Ok(bar)
}
fn shape(&self) -> Vec<usize> {
self.menus.iter().map(|(_, menu)| menu.len()).collect()
}
}
pub(super) fn decode_command(id: u16, shape: &[usize]) -> Option<MenuCommand> {
let id = usize::from(id);
let menu = (id / COMMAND_STRIDE).checked_sub(1)?;
let item = id % COMMAND_STRIDE;
(item < *shape.get(menu)?).then_some(MenuCommand { menu, item })
}
impl NativeWindow {
pub fn set_menu_bar(&mut self, bar: Option<&MenuBar>) -> io::Result<()> {
if self.is_destroyed() {
return Err(invalid("a destroyed native window has no menu bar"));
}
let built = bar.map(MenuBar::build).transpose()?;
let handle = built.as_ref().map(|menu| menu.0);
unsafe { SetMenu(self.hwnd, handle) }.map_err(windows_error)?;
let _ = built.map(OwnedMenu::into_raw);
if let Some(previous) = std::mem::replace(&mut self.menu_bar, handle) {
destroy(previous);
}
self.menu_shape = bar.map(MenuBar::shape).unwrap_or_default();
self.state.with(|state| state.menu_commands.clear());
let _ = unsafe { DrawMenuBar(self.hwnd) };
Ok(())
}
pub fn take_menu_commands(&mut self) -> Vec<MenuCommand> {
let shape = &self.menu_shape;
self.state.with(|state| {
state
.menu_commands
.drain(..)
.filter_map(|id| decode_command(id, shape))
.collect()
})
}
}
fn destroy(menu: HMENU) {
let _ = unsafe { DestroyMenu(menu) };
}