native_windows_gui2/controls/
menu.rs

1use super::{ControlBase, ControlHandle};
2use crate::NwgError;
3use crate::win32::menu as mh;
4use std::ptr;
5
6const NOT_BOUND: &'static str = "Menu/MenuItem is not yet bound to a winapi object";
7const BAD_HANDLE: &'static str = "INTERNAL ERROR: Menu/MenuItem handle is not HMENU!";
8
9bitflags! {
10    /**
11        Menu flags to use with the `Menu::popup_with_flags` function.
12        Using `PopupMenuFlags::empty` is the same as `ALIGN_LEFT|ALIGN_TOP|LEFT_BUTTON`
13
14        Aligment flags:
15
16        * ALIGN_LEFT:     Positions the shortcut menu so that its left side is aligned with the coordinate specified by the x parameter.
17        * ALIGN_H_CENTER: Centers the shortcut menu horizontally relative to the coordinate specified by the x parameter.
18        * ALIGN_RIGHT:    Positions the shortcut menu so that its right side is aligned with the coordinate specified by the x parameter.
19        * ALIGN_BOTTOM:   Positions the shortcut menu so that its bottom side is aligned with the coordinate specified by the y parameter.
20        * ALIGN_TOP:      Positions the shortcut menu so that its top side is aligned with the coordinate specified by the y parameter.
21        * ALIGN_V_CENTER: Centers the shortcut menu vertically relative to the coordinate specified by the y parameter.
22
23        Button flags:
24
25        * LEFT_BUTTON:    The user can select menu items with only the left mouse button.
26        * RIGHT_BUTTON:   The user can select menu items with both the left **AND** right mouse buttons.
27
28        Animations flags:
29
30        * ANIMATE_NONE:   Displays menu without animation.
31        * ANIMATE_RIGHT_TO_LEFT:  Animates the menu from right to left.
32        * ANIMATE_LEFT_TO_RIGHT:  Animates the menu from left to right.
33        * ANIMATE_BOTTOM_TO_TOP:  Animates the menu from bottom to top.
34        * ANIMATE_TOP_TO_BOTTOM: Animates the menu from top to bottom.
35    */
36    pub struct PopupMenuFlags: u32 {
37        const ALIGN_LEFT = 0x0000;
38        const ALIGN_H_CENTER = 0x0004;
39        const ALIGN_RIGHT = 0x0008;
40
41        const ALIGN_BOTTOM = 0x0020;
42        const ALIGN_TOP = 0x0000;
43        const ALIGN_V_CENTER = 0x0010;
44
45        const LEFT_BUTTON = 0x0000;
46        const RIGHT_BUTTON = 0x0002;
47
48        const ANIMATE_NONE = 0x4000;
49        const ANIMATE_RIGHT_TO_LEFT = 0x8000;
50        const ANIMATE_LEFT_TO_RIGHT = 0x4000;
51        const ANIMATE_BOTTOM_TO_TOP = 0x2000;
52        const ANIMATE_TOP_TO_BOTTOM = 0x1000;
53    }
54}
55
56/**
57    A windows menu. Can represent a menu in a window menubar, a context menu, or a submenu in another menu
58
59    Requires the `menu` feature.
60
61    **Builder parameters:**
62      - text: The text of the menu
63      - disabled: If the menu can be selected by the user
64      - popup: The menu is a context menu
65      - parent: A top level window, a menu or None. With a top level window, the menu is added to the menu bar if popup is set to false.
66
67    **Control events:**
68      - OnMenuOpen: Sent when a drop-down menu or submenu is about to become active.
69      - OnMenuHover: When the user hovers the menu
70      - OnMenuEnter: When the user enters the menu. Technically, when the user enters the menu modal loop.
71      - OnMenuExit: When the menu is closed. Technically, when the user exits the menu modal loop.
72
73    **Menu Access Keys**
74
75    Menu can have access keys. An access key is an underlined letter in the text of a menu item.
76    When a menu is active, the user can select a menu item by pressing the key that corresponds to the item's underlined letter.
77    The user makes the menu bar active by pressing the ALT key to highlight the first item on the menu bar.
78    A menu is active when it is displayed.
79
80    To create an access key for a menu item, precede any character in the item's text string with an ampersand.
81    For example, the text string "&Move" causes the system to underline the letter "M".
82
83    ```rust
84    use native_windows_gui2 as nwg;
85
86    fn menu(menu: &mut nwg::Menu, window: &nwg::Window) -> Result<(), nwg::NwgError> {
87        nwg::Menu::builder()
88            .text("&Hello")
89            .disabled(false)
90            .parent(window)
91            .build(menu)
92    }
93    ```
94*/
95#[derive(Default, PartialEq, Eq)]
96pub struct Menu {
97    pub handle: ControlHandle,
98}
99
100impl Menu {
101    pub fn builder<'a>() -> MenuBuilder<'a> {
102        MenuBuilder {
103            text: "Menu",
104            disabled: false,
105            popup: false,
106            parent: None,
107        }
108    }
109
110    /// Return true if the control user can interact with the control, return false otherwise
111    pub fn enabled(&self) -> bool {
112        if self.handle.blank() {
113            panic!("{}", NOT_BOUND);
114        }
115        let (parent_handle, handle) = match self.handle {
116            ControlHandle::Menu(parent, menu) => (parent, menu),
117            ControlHandle::PopMenu(_, _) => {
118                return true;
119            }
120            _ => panic!("{}", BAD_HANDLE),
121        };
122
123        mh::is_menu_enabled(parent_handle, handle)
124    }
125
126    /// Enable or disable the control
127    /// A popup menu cannot be disabled
128    pub fn set_enabled(&self, v: bool) {
129        if self.handle.blank() {
130            panic!("{}", NOT_BOUND);
131        }
132        let (parent_handle, handle) = match self.handle {
133            ControlHandle::Menu(parent, menu) => (parent, menu),
134            ControlHandle::PopMenu(_, _) => {
135                return;
136            }
137            _ => panic!("{}", BAD_HANDLE),
138        };
139
140        mh::enable_menu(parent_handle, handle, v);
141    }
142
143    /// Show a popup menu as the selected position. Do nothing for menubar menu.
144    pub fn popup_with_flags(&self, x: i32, y: i32, flags: PopupMenuFlags) {
145        use winapi::ctypes::c_int;
146        use winapi::um::winuser::{SetForegroundWindow, TrackPopupMenu};
147
148        if self.handle.blank() {
149            panic!("Menu is not bound");
150        }
151        let (parent_handle, handle) = match self.handle.pop_hmenu() {
152            Some(v) => v,
153            None => {
154                return;
155            }
156        };
157
158        unsafe {
159            SetForegroundWindow(parent_handle);
160            TrackPopupMenu(
161                handle,
162                flags.bits(),
163                x as c_int,
164                y as c_int,
165                0,
166                parent_handle,
167                ptr::null(),
168            );
169        }
170    }
171
172    /// Show a popup menu as the selected position. Do nothing for menubar menu.
173    pub fn popup(&self, x: i32, y: i32) {
174        self.popup_with_flags(x, y, PopupMenuFlags::empty())
175    }
176}
177
178impl Drop for Menu {
179    fn drop(&mut self) {
180        self.handle.destroy();
181    }
182}
183
184pub struct MenuBuilder<'a> {
185    text: &'a str,
186    disabled: bool,
187    popup: bool,
188    parent: Option<ControlHandle>,
189}
190
191impl<'a> MenuBuilder<'a> {
192    pub fn text(mut self, text: &'a str) -> MenuBuilder<'a> {
193        self.text = text;
194        self
195    }
196
197    pub fn disabled(mut self, disabled: bool) -> MenuBuilder<'a> {
198        self.disabled = disabled;
199        self
200    }
201
202    pub fn popup(mut self, popup: bool) -> MenuBuilder<'a> {
203        self.popup = popup;
204        self
205    }
206
207    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> MenuBuilder<'a> {
208        self.parent = Some(p.into());
209        self
210    }
211
212    pub fn build(self, menu: &mut Menu) -> Result<(), NwgError> {
213        if self.parent.is_none() {
214            return Err(NwgError::no_parent_menu());
215        }
216
217        menu.handle = ControlBase::build_hmenu()
218            .text(self.text)
219            .item(false)
220            .popup(self.popup)
221            .parent(self.parent.unwrap())
222            .build()?;
223
224        if self.disabled {
225            menu.set_enabled(false)
226        }
227
228        Ok(())
229    }
230}
231
232/**
233    A windows menu item. Can be added to a menubar or another menu.
234
235    Requires the `menu` feature.
236
237   **Builder parameters:**
238      - text: The text of the menu, including access key and shortcut label
239      - disabled: If the item can be selected by the user
240      - check: If the item should have a check mark next to it.
241      - parent: A top level window or a menu. With a top level window, the menu item is added to the menu bar.
242
243   **Control events:**
244      - OnMenuItemSelected: When a menu item is selected. This can be done by clicking or using the hot-key.
245      - OnMenuHover: When the user hovers the menu
246
247
248    **Menu Access Keys**
249
250    Just like Menus, menu items can have access keys. An access key is an underlined letter in the text of a menu item.
251    When a menu is active, the user can select a menu item by pressing the key that corresponds to the item's underlined letter.
252    The user makes the menu bar active by pressing the ALT key to highlight the first item on the menu bar.
253    A menu is active when it is displayed.
254
255    To create an access key for a menu item, precede any character in the item's text string with an ampersand.
256    For example, the text string "&Move" causes the system to underline the letter "M".
257
258    ```rust
259    use native_windows_gui2 as nwg;
260
261    fn menu_item(item: &mut nwg::MenuItem, menu: &nwg::Menu) -> Result<(), nwg::NwgError> {
262        nwg::MenuItem::builder()
263            .text("&Hello")
264            .disabled(true)
265            .parent(menu)
266            .build(item)
267    }
268    ```
269
270    **Shortcut Label**
271
272    A shortcut label like "Ctrl+O" can be added with the `text` field. By prefixing the shortcut with a tab character `\t` and
273    adding it as a suffix to the text label, it will be rendered right-aligned in the menu item.
274
275    For example, an "Exit" menu item could have both an access key "E" and a shortcut label "Alt+F4" with `text: "&Exit\tAlt+F4"`.
276
277    **note:** This will only add a text label to the menu item, the keyboard handling must be done through other means.
278
279    ```rust
280    use native_windows_gui2 as nwg;
281
282    fn menu(menu: &mut nwg::Menu, window: &nwg::Window) -> Result<(), nwg::NwgError> {
283        nwg::Menu::builder()
284            .text("&Exit\tAlt+F4")
285            .disabled(false)
286            .parent(window)
287            .build(menu)
288    }
289    ```
290*/
291#[derive(Default, Debug, PartialEq, Eq)]
292pub struct MenuItem {
293    pub handle: ControlHandle,
294}
295
296impl MenuItem {
297    pub fn builder<'a>() -> MenuItemBuilder<'a> {
298        MenuItemBuilder {
299            text: "Menu Item",
300            disabled: false,
301            check: false,
302            parent: None,
303        }
304    }
305
306    /// Return true if the control user can interact with the control, return false otherwise
307    pub fn enabled(&self) -> bool {
308        if self.handle.blank() {
309            panic!("{}", NOT_BOUND);
310        }
311        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
312
313        mh::is_menuitem_enabled(parent_handle, None, Some(id))
314    }
315
316    /// Enable or disable the control
317    pub fn set_enabled(&self, v: bool) {
318        if self.handle.blank() {
319            panic!("{}", NOT_BOUND);
320        }
321        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
322
323        mh::enable_menuitem(parent_handle, None, Some(id), v);
324    }
325
326    /// Sets the check state of a menu item
327    pub fn set_checked(&self, check: bool) {
328        if self.handle.blank() {
329            panic!("{}", NOT_BOUND);
330        }
331        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
332
333        mh::check_menu_item(parent_handle, id, check);
334    }
335
336    /// Returns the check state of a menu item
337    pub fn checked(&self) -> bool {
338        if self.handle.blank() {
339            panic!("{}", NOT_BOUND);
340        }
341        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
342
343        mh::menu_item_checked(parent_handle, id)
344    }
345}
346
347impl Drop for MenuItem {
348    fn drop(&mut self) {
349        self.handle.destroy();
350    }
351}
352
353pub struct MenuItemBuilder<'a> {
354    text: &'a str,
355    disabled: bool,
356    check: bool,
357    parent: Option<ControlHandle>,
358}
359
360impl<'a> MenuItemBuilder<'a> {
361    pub fn text(mut self, text: &'a str) -> MenuItemBuilder<'a> {
362        self.text = text;
363        self
364    }
365
366    pub fn disabled(mut self, disabled: bool) -> MenuItemBuilder<'a> {
367        self.disabled = disabled;
368        self
369    }
370
371    pub fn check(mut self, check: bool) -> MenuItemBuilder<'a> {
372        self.check = check;
373        self
374    }
375
376    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> MenuItemBuilder<'a> {
377        self.parent = Some(p.into());
378        self
379    }
380
381    pub fn build(self, item: &mut MenuItem) -> Result<(), NwgError> {
382        if self.parent.is_none() {
383            return Err(NwgError::no_parent_menu());
384        }
385
386        item.handle = ControlBase::build_hmenu()
387            .text(self.text)
388            .item(true)
389            .parent(self.parent.unwrap())
390            .build()?;
391
392        if self.disabled {
393            item.set_enabled(false);
394        }
395
396        if self.check {
397            item.set_checked(true);
398        }
399
400        Ok(())
401    }
402}
403
404/**
405    A menu separator. Can be added between two menu item to separte them. Cannot be added to a menubar.
406
407    Requires the `menu` feature.
408
409    **Builder parameters:**
410      - parent: A top level window or a menu. With a top level window, the menu item is added to the menu bar.
411
412   **Control events:**
413      - OnMenuHover: When the user hovers the menu
414
415    ```rust
416    use native_windows_gui2 as nwg;
417
418    fn separator(sep: &mut nwg::MenuSeparator, menu: &nwg::Menu) -> Result<(), nwg::NwgError> {
419        nwg::MenuSeparator::builder()
420            .parent(menu)
421            .build(sep)
422    }
423    ```
424*/
425#[derive(Default, Debug, PartialEq, Eq)]
426pub struct MenuSeparator {
427    pub handle: ControlHandle,
428}
429
430impl MenuSeparator {
431    pub fn builder() -> MenuSeparatorBuilder {
432        MenuSeparatorBuilder { parent: None }
433    }
434}
435
436pub struct MenuSeparatorBuilder {
437    parent: Option<ControlHandle>,
438}
439
440impl MenuSeparatorBuilder {
441    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> MenuSeparatorBuilder {
442        self.parent = Some(p.into());
443        self
444    }
445
446    pub fn build(self, sep: &mut MenuSeparator) -> Result<(), NwgError> {
447        if self.parent.is_none() {
448            return Err(NwgError::no_parent_menu());
449        }
450
451        sep.handle = ControlBase::build_hmenu()
452            .separator(true)
453            .parent(self.parent.unwrap())
454            .build()?;
455
456        Ok(())
457    }
458}
459
460impl Drop for MenuSeparator {
461    fn drop(&mut self) {
462        self.handle.destroy();
463    }
464}