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}