muda_win/items/
submenu.rs

1use std::{cell::RefCell, mem, rc::Rc};
2
3use crate::{
4    dpi::Position, sealed::IsMenuItemBase, util::AddOp, ContextMenu, IsMenuItem, MenuId,
5    MenuItemKind,
6};
7
8/// A menu that can be added to a [`Menu`] or another [`Submenu`].
9///
10/// [`Menu`]: crate::Menu
11#[derive(Clone)]
12pub struct Submenu {
13    pub(crate) id: Rc<MenuId>,
14    pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
15}
16
17impl IsMenuItemBase for Submenu {}
18impl IsMenuItem for Submenu {
19    fn kind(&self) -> MenuItemKind {
20        MenuItemKind::Submenu(self.clone())
21    }
22
23    fn id(&self) -> &MenuId {
24        self.id()
25    }
26
27    fn into_id(self) -> MenuId {
28        self.into_id()
29    }
30}
31
32impl Submenu {
33    /// Create a new submenu.
34    ///
35    /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
36    ///   for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
37    pub fn new<S: AsRef<str>>(text: S, enabled: bool) -> Self {
38        let submenu = crate::platform_impl::MenuChild::new_submenu(text.as_ref(), enabled, None);
39        Self {
40            id: Rc::new(submenu.id().clone()),
41            inner: Rc::new(RefCell::new(submenu)),
42        }
43    }
44
45    /// Create a new submenu with the specified id.
46    ///
47    /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
48    ///   for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
49    pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S, enabled: bool) -> Self {
50        let id = id.into();
51
52        Self {
53            id: Rc::new(id.clone()),
54            inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new_submenu(
55                text.as_ref(),
56                enabled,
57                Some(id),
58            ))),
59        }
60    }
61
62    /// Creates a new submenu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
63    pub fn with_items<S: AsRef<str>>(
64        text: S,
65        enabled: bool,
66        items: &[&dyn IsMenuItem],
67    ) -> crate::Result<Self> {
68        let menu = Self::new(text, enabled);
69        menu.append_items(items)?;
70        Ok(menu)
71    }
72
73    /// Creates a new submenu with the specified id and given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
74    pub fn with_id_and_items<I: Into<MenuId>, S: AsRef<str>>(
75        id: I,
76        text: S,
77        enabled: bool,
78        items: &[&dyn IsMenuItem],
79    ) -> crate::Result<Self> {
80        let menu = Self::with_id(id, text, enabled);
81        menu.append_items(items)?;
82        Ok(menu)
83    }
84
85    /// Returns a unique identifier associated with this submenu.
86    pub fn id(&self) -> &MenuId {
87        &self.id
88    }
89
90    /// Add a menu item to the end of this menu.
91    pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
92        self.inner.borrow_mut().add_menu_item(item, AddOp::Append)
93    }
94
95    /// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop.
96    pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
97        for item in items {
98            self.append(*item)?
99        }
100
101        Ok(())
102    }
103
104    /// Add a menu item to the beginning of this submenu.
105    pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
106        self.inner
107            .borrow_mut()
108            .add_menu_item(item, AddOp::Insert(0))
109    }
110
111    /// Add menu items to the beginning of this submenu.
112    /// It calls [`Menu::prepend`](crate::Menu::prepend) on the first element and
113    /// passes the rest to [`Menu::insert_items`](crate::Menu::insert_items) with position of `1`.
114    pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
115        self.insert_items(items, 0)
116    }
117
118    /// Insert a menu item at the specified `postion` in the submenu.
119    pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> {
120        self.inner
121            .borrow_mut()
122            .add_menu_item(item, AddOp::Insert(position))
123    }
124
125    /// Insert menu items at the specified `postion` in the submenu.
126    pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> {
127        for (i, item) in items.iter().enumerate() {
128            self.insert(*item, position + i)?
129        }
130
131        Ok(())
132    }
133
134    /// Remove a menu item from this submenu.
135    pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
136        self.inner.borrow_mut().remove(item)
137    }
138
139    /// Remove the menu item at the specified position from this submenu and returns it.
140    pub fn remove_at(&self, position: usize) -> Option<MenuItemKind> {
141        let mut items = self.items();
142        if items.len() > position {
143            let item = items.remove(position);
144            let _ = self.remove(item.as_ref());
145            Some(item)
146        } else {
147            None
148        }
149    }
150
151    /// Returns a list of menu items that has been added to this submenu.
152    pub fn items(&self) -> Vec<MenuItemKind> {
153        self.inner.borrow().items()
154    }
155
156    /// Get the text for this submenu.
157    pub fn text(&self) -> String {
158        self.inner.borrow().text()
159    }
160
161    /// Set the text for this submenu. `text` could optionally contain
162    /// an `&` before a character to assign this character as the mnemonic
163    /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
164    pub fn set_text<S: AsRef<str>>(&self, text: S) {
165        self.inner.borrow_mut().set_text(text.as_ref())
166    }
167
168    /// Get whether this submenu is enabled or not.
169    pub fn is_enabled(&self) -> bool {
170        self.inner.borrow().is_enabled()
171    }
172
173    /// Enable or disable this submenu.
174    pub fn set_enabled(&self, enabled: bool) {
175        self.inner.borrow_mut().set_enabled(enabled)
176    }
177
178    /// Convert this submenu into its menu ID.
179    pub fn into_id(mut self) -> MenuId {
180        // Note: `Rc::into_inner` is available from Rust 1.70
181        if let Some(id) = Rc::get_mut(&mut self.id) {
182            mem::take(id)
183        } else {
184            self.id().clone()
185        }
186    }
187}
188
189impl ContextMenu for Submenu {
190    fn hpopupmenu(&self) -> isize {
191        self.inner.borrow().hpopupmenu()
192    }
193
194    unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) -> bool {
195        self.inner
196            .borrow_mut()
197            .show_context_menu_for_hwnd(hwnd, position)
198    }
199
200    unsafe fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
201        self.inner.borrow().attach_menu_subclass_for_hwnd(hwnd)
202    }
203
204    unsafe fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) {
205        self.inner.borrow().detach_menu_subclass_from_hwnd(hwnd)
206    }
207}