muda_win/
menu.rs

1use std::{cell::RefCell, rc::Rc};
2
3use crate::{dpi::Position, util::AddOp, ContextMenu, IsMenuItem, MenuId, MenuItemKind};
4
5/// A root menu that can be added to a Window on Windows and Linux
6/// and used as the app global menu on macOS.
7#[derive(Clone)]
8pub struct Menu {
9    id: Rc<MenuId>,
10    inner: Rc<RefCell<crate::platform_impl::Menu>>,
11}
12
13impl Default for Menu {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl Menu {
20    /// Creates a new menu.
21    pub fn new() -> Self {
22        let menu = crate::platform_impl::Menu::new(None);
23        Self {
24            id: Rc::new(menu.id().clone()),
25            inner: Rc::new(RefCell::new(menu)),
26        }
27    }
28
29    /// Creates a new menu with the specified id.
30    pub fn with_id<I: Into<MenuId>>(id: I) -> Self {
31        let id = id.into();
32        Self {
33            id: Rc::new(id.clone()),
34            inner: Rc::new(RefCell::new(crate::platform_impl::Menu::new(Some(id)))),
35        }
36    }
37
38    /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally.
39    pub fn with_items(items: &[&dyn IsMenuItem]) -> crate::Result<Self> {
40        let menu = Self::new();
41        menu.append_items(items)?;
42        Ok(menu)
43    }
44
45    /// Creates a new menu with the specified id and given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally.
46    pub fn with_id_and_items<I: Into<MenuId>>(
47        id: I,
48        items: &[&dyn IsMenuItem],
49    ) -> crate::Result<Self> {
50        let menu = Self::with_id(id);
51        menu.append_items(items)?;
52        Ok(menu)
53    }
54
55    /// Returns a unique identifier associated with this menu.
56    pub fn id(&self) -> &MenuId {
57        &self.id
58    }
59
60    /// Add a menu item to the end of this menu.
61    pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
62        self.inner.borrow_mut().add_menu_item(item, AddOp::Append)
63    }
64
65    /// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally.
66    pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
67        for item in items {
68            self.append(*item)?
69        }
70
71        Ok(())
72    }
73
74    /// Add a menu item to the beginning of this menu.
75    pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
76        self.inner
77            .borrow_mut()
78            .add_menu_item(item, AddOp::Insert(0))
79    }
80
81    /// Add menu items to the beginning of this menu. It calls [`Menu::insert_items`] with position of `0` internally.
82    pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
83        self.insert_items(items, 0)
84    }
85
86    /// Insert a menu item at the specified `postion` in the menu.
87    pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> {
88        self.inner
89            .borrow_mut()
90            .add_menu_item(item, AddOp::Insert(position))
91    }
92
93    /// Insert menu items at the specified `postion` in the menu.
94    pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> {
95        for (i, item) in items.iter().enumerate() {
96            self.insert(*item, position + i)?
97        }
98
99        Ok(())
100    }
101
102    /// Remove a menu item from this menu.
103    pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
104        self.inner.borrow_mut().remove(item)
105    }
106
107    /// Remove the menu item at the specified position from this menu and returns it.
108    pub fn remove_at(&self, position: usize) -> Option<MenuItemKind> {
109        let mut items = self.items();
110        if items.len() > position {
111            let item = items.remove(position);
112            let _ = self.remove(item.as_ref());
113            Some(item)
114        } else {
115            None
116        }
117    }
118
119    /// Returns a list of menu items that has been added to this menu.
120    pub fn items(&self) -> Vec<MenuItemKind> {
121        self.inner.borrow().items()
122    }
123
124    /// Adds this menu to a win32 window.
125    ///
126    /// # Safety
127    ///
128    /// The `hwnd` must be a valid window HWND.
129    ///
130    /// ##  Note about accelerators:
131    ///
132    /// For accelerators to work, the event loop needs to call
133    /// [`TranslateAcceleratorW`](windows_sys::Win32::UI::WindowsAndMessaging::TranslateAcceleratorW)
134    /// with the [`HACCEL`](windows_sys::Win32::UI::WindowsAndMessaging::HACCEL) returned from [`Menu::haccel`]
135    ///
136    /// #### Example:
137    /// ```no_run
138    /// # use muda_win::Menu;
139    /// # use windows_sys::Win32::UI::WindowsAndMessaging::{MSG, GetMessageW, TranslateMessage, DispatchMessageW, TranslateAcceleratorW};
140    /// let menu = Menu::new();
141    /// unsafe {
142    ///     let mut msg: MSG = std::mem::zeroed();
143    ///     while GetMessageW(&mut msg, std::ptr::null_mut(), 0, 0) == 1 {
144    ///         let translated = TranslateAcceleratorW(msg.hwnd, menu.haccel() as _, &msg as *const _);
145    ///         if translated != 1{
146    ///             TranslateMessage(&msg);
147    ///             DispatchMessageW(&msg);
148    ///         }
149    ///     }
150    /// }
151    /// ```
152    pub unsafe fn init_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
153        self.inner.borrow_mut().init_for_hwnd(hwnd)
154    }
155
156    /// Adds this menu to a win32 window using the specified theme.
157    ///
158    /// See [Menu::init_for_hwnd] for more info.
159    ///
160    /// Note that the theme only affects the menu bar itself and not submenus or context menu.
161    ///
162    /// # Safety
163    ///
164    /// The `hwnd` must be a valid window HWND.
165    pub unsafe fn init_for_hwnd_with_theme(
166        &self,
167        hwnd: isize,
168        theme: MenuTheme,
169    ) -> crate::Result<()> {
170        self.inner
171            .borrow_mut()
172            .init_for_hwnd_with_theme(hwnd, theme)
173    }
174
175    /// Set a theme for the menu bar on this window.
176    ///
177    /// Note that the theme only affects the menu bar itself and not submenus or context menu.
178    ///
179    /// # Safety
180    ///
181    /// The `hwnd` must be a valid window HWND.
182    pub unsafe fn set_theme_for_hwnd(&self, hwnd: isize, theme: MenuTheme) -> crate::Result<()> {
183        self.inner.borrow().set_theme_for_hwnd(hwnd, theme)
184    }
185
186    /// Returns The [`HACCEL`](windows_sys::Win32::UI::WindowsAndMessaging::HACCEL) associated with this menu
187    /// It can be used with [`TranslateAcceleratorW`](windows_sys::Win32::UI::WindowsAndMessaging::TranslateAcceleratorW)
188    /// in the event loop to enable accelerators
189    ///
190    /// The returned [`HACCEL`](windows_sys::Win32::UI::WindowsAndMessaging::HACCEL) is valid as long as the [Menu] is.
191    pub fn haccel(&self) -> isize {
192        self.inner.borrow_mut().haccel()
193    }
194
195    /// Removes this menu from a win32 window
196    ///
197    /// # Safety
198    ///
199    /// The `hwnd` must be a valid window HWND.
200    pub unsafe fn remove_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
201        self.inner.borrow_mut().remove_for_hwnd(hwnd)
202    }
203
204    /// Hides this menu from a win32 window
205    ///
206    /// # Safety
207    ///
208    /// The `hwnd` must be a valid window HWND.
209    pub unsafe fn hide_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
210        self.inner.borrow().hide_for_hwnd(hwnd)
211    }
212
213    /// Shows this menu on a win32 window
214    ///
215    /// # Safety
216    ///
217    /// The `hwnd` must be a valid window HWND.
218    pub unsafe fn show_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
219        self.inner.borrow().show_for_hwnd(hwnd)
220    }
221
222    /// Returns whether this menu visible on a on a win32 window
223    ///
224    /// # Safety
225    ///
226    /// The `hwnd` must be a valid window HWND.
227    pub unsafe fn is_visible_on_hwnd(&self, hwnd: isize) -> bool {
228        self.inner.borrow().is_visible_on_hwnd(hwnd)
229    }
230}
231
232impl ContextMenu for Menu {
233    fn hpopupmenu(&self) -> isize {
234        self.inner.borrow().hpopupmenu()
235    }
236
237    unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) -> bool {
238        self.inner
239            .borrow_mut()
240            .show_context_menu_for_hwnd(hwnd, position)
241    }
242
243    unsafe fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
244        self.inner.borrow().attach_menu_subclass_for_hwnd(hwnd)
245    }
246
247    unsafe fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) {
248        self.inner.borrow().detach_menu_subclass_from_hwnd(hwnd)
249    }
250}
251
252/// The window menu bar theme
253#[repr(usize)]
254#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256pub enum MenuTheme {
257    Dark = 0,
258    Light = 1,
259    Auto = 2,
260}