muda/items/
submenu.rs

1// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.inner
3// SPDX-License-Identifier: MIT
4
5use std::{cell::RefCell, mem, rc::Rc};
6
7use crate::{
8    dpi::Position, sealed::IsMenuItemBase, util::AddOp, ContextMenu, Icon, IsMenuItem, MenuId,
9    MenuItemKind, NativeIcon,
10};
11
12/// A menu that can be added to a [`Menu`] or another [`Submenu`].
13///
14/// [`Menu`]: crate::Menu
15#[derive(Clone)]
16pub struct Submenu {
17    pub(crate) id: Rc<MenuId>,
18    pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
19}
20
21impl IsMenuItemBase for Submenu {}
22impl IsMenuItem for Submenu {
23    fn kind(&self) -> MenuItemKind {
24        MenuItemKind::Submenu(self.clone())
25    }
26
27    fn id(&self) -> &MenuId {
28        self.id()
29    }
30
31    fn into_id(self) -> MenuId {
32        self.into_id()
33    }
34}
35
36impl Submenu {
37    /// Create a new submenu.
38    ///
39    /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
40    ///   for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
41    pub fn new<S: AsRef<str>>(text: S, enabled: bool) -> Self {
42        let submenu = crate::platform_impl::MenuChild::new_submenu(text.as_ref(), enabled, None);
43        Self {
44            id: Rc::new(submenu.id().clone()),
45            inner: Rc::new(RefCell::new(submenu)),
46        }
47    }
48
49    /// Create a new submenu with the specified id.
50    ///
51    /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
52    ///   for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
53    pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S, enabled: bool) -> Self {
54        let id = id.into();
55
56        Self {
57            id: Rc::new(id.clone()),
58            inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new_submenu(
59                text.as_ref(),
60                enabled,
61                Some(id),
62            ))),
63        }
64    }
65
66    /// Creates a new submenu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
67    pub fn with_items<S: AsRef<str>>(
68        text: S,
69        enabled: bool,
70        items: &[&dyn IsMenuItem],
71    ) -> crate::Result<Self> {
72        let menu = Self::new(text, enabled);
73        menu.append_items(items)?;
74        Ok(menu)
75    }
76
77    /// Creates a new submenu with the specified id and given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
78    pub fn with_id_and_items<I: Into<MenuId>, S: AsRef<str>>(
79        id: I,
80        text: S,
81        enabled: bool,
82        items: &[&dyn IsMenuItem],
83    ) -> crate::Result<Self> {
84        let menu = Self::with_id(id, text, enabled);
85        menu.append_items(items)?;
86        Ok(menu)
87    }
88
89    /// Returns a unique identifier associated with this submenu.
90    pub fn id(&self) -> &MenuId {
91        &self.id
92    }
93
94    /// Add a menu item to the end of this menu.
95    pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
96        self.inner.borrow_mut().add_menu_item(item, AddOp::Append)
97    }
98
99    /// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop.
100    pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
101        for item in items {
102            self.append(*item)?
103        }
104
105        Ok(())
106    }
107
108    /// Add a menu item to the beginning of this submenu.
109    pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
110        self.inner
111            .borrow_mut()
112            .add_menu_item(item, AddOp::Insert(0))
113    }
114
115    /// Add menu items to the beginning of this submenu.
116    /// It calls [`Menu::prepend`](crate::Menu::prepend) on the first element and
117    /// passes the rest to [`Menu::insert_items`](crate::Menu::insert_items) with position of `1`.
118    pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
119        self.insert_items(items, 0)
120    }
121
122    /// Insert a menu item at the specified `postion` in the submenu.
123    pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> {
124        self.inner
125            .borrow_mut()
126            .add_menu_item(item, AddOp::Insert(position))
127    }
128
129    /// Insert menu items at the specified `postion` in the submenu.
130    pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> {
131        for (i, item) in items.iter().enumerate() {
132            self.insert(*item, position + i)?
133        }
134
135        Ok(())
136    }
137
138    /// Remove a menu item from this submenu.
139    pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
140        self.inner.borrow_mut().remove(item)
141    }
142
143    /// Remove the menu item at the specified position from this submenu and returns it.
144    pub fn remove_at(&self, position: usize) -> Option<MenuItemKind> {
145        let mut items = self.items();
146        if items.len() > position {
147            let item = items.remove(position);
148            let _ = self.remove(item.as_ref());
149            Some(item)
150        } else {
151            None
152        }
153    }
154
155    /// Returns a list of menu items that has been added to this submenu.
156    pub fn items(&self) -> Vec<MenuItemKind> {
157        self.inner.borrow().items()
158    }
159
160    /// Get the text for this submenu.
161    pub fn text(&self) -> String {
162        self.inner.borrow().text()
163    }
164
165    /// Set the text for this submenu. `text` could optionally contain
166    /// an `&` before a character to assign this character as the mnemonic
167    /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
168    pub fn set_text<S: AsRef<str>>(&self, text: S) {
169        self.inner.borrow_mut().set_text(text.as_ref())
170    }
171
172    /// Get whether this submenu is enabled or not.
173    pub fn is_enabled(&self) -> bool {
174        self.inner.borrow().is_enabled()
175    }
176
177    /// Enable or disable this submenu.
178    pub fn set_enabled(&self, enabled: bool) {
179        self.inner.borrow_mut().set_enabled(enabled)
180    }
181
182    /// Set this submenu as the Window menu for the application on macOS.
183    ///
184    /// This will cause macOS to automatically add window-switching items and
185    /// certain other items to the menu.
186    #[cfg(target_os = "macos")]
187    pub fn set_as_windows_menu_for_nsapp(&self) {
188        self.inner.borrow_mut().set_as_windows_menu_for_nsapp()
189    }
190
191    /// Set this submenu as the Help menu for the application on macOS.
192    ///
193    /// This will cause macOS to automatically add a search box to the menu.
194    ///
195    /// If no menu is set as the Help menu, macOS will automatically use any menu
196    /// which has a title matching the localized word "Help".
197    #[cfg(target_os = "macos")]
198    pub fn set_as_help_menu_for_nsapp(&self) {
199        self.inner.borrow_mut().set_as_help_menu_for_nsapp()
200    }
201
202    /// Convert this submenu into its menu ID.
203    pub fn into_id(mut self) -> MenuId {
204        // Note: `Rc::into_inner` is available from Rust 1.70
205        if let Some(id) = Rc::get_mut(&mut self.id) {
206            mem::take(id)
207        } else {
208            self.id().clone()
209        }
210    }
211
212    /// Change this menu item icon or remove it.
213    pub fn set_icon(&self, icon: Option<Icon>) {
214        self.inner.borrow_mut().set_icon(icon)
215    }
216
217    /// Change this menu item icon to a native image or remove it.
218    ///
219    /// ## Platform-specific:
220    ///
221    /// - **Windows / Linux**: Unsupported.
222    pub fn set_native_icon(&self, _icon: Option<NativeIcon>) {
223        #[cfg(target_os = "macos")]
224        self.inner.borrow_mut().set_native_icon(_icon)
225    }
226}
227
228impl ContextMenu for Submenu {
229    #[cfg(target_os = "windows")]
230    fn hpopupmenu(&self) -> isize {
231        self.inner.borrow().hpopupmenu()
232    }
233
234    #[cfg(target_os = "windows")]
235    unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) -> bool {
236        self.inner
237            .borrow_mut()
238            .show_context_menu_for_hwnd(hwnd, position)
239    }
240
241    #[cfg(target_os = "windows")]
242    unsafe fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
243        self.inner.borrow().attach_menu_subclass_for_hwnd(hwnd)
244    }
245
246    #[cfg(target_os = "windows")]
247    unsafe fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) {
248        self.inner.borrow().detach_menu_subclass_from_hwnd(hwnd)
249    }
250
251    #[cfg(all(target_os = "linux", feature = "gtk"))]
252    fn show_context_menu_for_gtk_window(
253        &self,
254        w: &gtk::Window,
255        position: Option<Position>,
256    ) -> bool {
257        self.inner
258            .borrow_mut()
259            .show_context_menu_for_gtk_window(w, position)
260    }
261
262    #[cfg(all(target_os = "linux", feature = "gtk"))]
263    fn gtk_context_menu(&self) -> gtk::Menu {
264        self.inner.borrow_mut().gtk_context_menu()
265    }
266
267    #[cfg(target_os = "macos")]
268    unsafe fn show_context_menu_for_nsview(
269        &self,
270        view: *const std::ffi::c_void,
271        position: Option<Position>,
272    ) -> bool {
273        self.inner
274            .borrow_mut()
275            .show_context_menu_for_nsview(view, position)
276    }
277
278    #[cfg(target_os = "macos")]
279    fn ns_menu(&self) -> *mut std::ffi::c_void {
280        self.inner.borrow().ns_menu()
281    }
282
283    fn as_submenu(&self) -> Option<&Submenu> {
284        Some(self)
285    }
286}