native_windows_gui/controls/
menu.rs

1use crate::win32::menu as mh;
2use crate::NwgError;
3use super::{ControlBase, ControlHandle};
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_gui 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
102    pub fn builder<'a>() -> MenuBuilder<'a> {
103        MenuBuilder {
104            text: "Menu",
105            disabled: false,
106            popup: false,
107            parent: None
108        }
109    }
110
111    /// Return true if the control user can interact with the control, return false otherwise
112    pub fn enabled(&self) -> bool {
113        if self.handle.blank() { panic!("{}", NOT_BOUND); }
114        let (parent_handle, handle) = match self.handle {
115            ControlHandle::Menu(parent, menu) => (parent, menu),
116            ControlHandle::PopMenu(_, _) => { return true; },
117            _ => panic!("{}", BAD_HANDLE)
118        };
119
120        unsafe { mh::is_menu_enabled(parent_handle, handle) }
121    }
122
123    /// Enable or disable the control
124    /// A popup menu cannot be disabled
125    pub fn set_enabled(&self, v: bool) {
126        if self.handle.blank() { panic!("{}", NOT_BOUND); }
127        let (parent_handle, handle) = match self.handle {
128            ControlHandle::Menu(parent, menu) => (parent, menu),
129            ControlHandle::PopMenu(_, _) => { return; },
130            _ => panic!("{}", BAD_HANDLE)
131        };
132
133        unsafe { mh::enable_menu(parent_handle, handle, v); }
134    }
135
136    /// Show a popup menu as the selected position. Do nothing for menubar menu.
137    pub fn popup_with_flags(&self, x: i32, y: i32, flags: PopupMenuFlags) {
138        use winapi::um::winuser::{TrackPopupMenu, SetForegroundWindow};
139        use winapi::ctypes::c_int;
140
141        if self.handle.blank() { panic!("Menu is not bound"); }
142        let (parent_handle, handle) = match self.handle.pop_hmenu() {
143            Some(v) => v,
144            None => { return; }
145        };
146
147        unsafe { 
148            SetForegroundWindow(parent_handle);
149            TrackPopupMenu(
150                handle,
151                flags.bits(),
152                x as c_int,
153                y as c_int,
154                0,
155                parent_handle,
156                ptr::null()
157            );
158        }
159    }
160
161    /// Show a popup menu as the selected position. Do nothing for menubar menu.
162    pub fn popup(&self, x: i32, y: i32) {
163        self.popup_with_flags(x, y, PopupMenuFlags::empty())
164    }
165
166}
167
168impl Drop for Menu {
169    fn drop(&mut self) {
170        self.handle.destroy();
171    }
172}
173
174pub struct MenuBuilder<'a> {
175    text: &'a str,
176    disabled: bool,
177    popup: bool,
178    parent: Option<ControlHandle>
179}
180
181impl<'a> MenuBuilder<'a> {
182
183    pub fn text(mut self, text: &'a str) -> MenuBuilder<'a> {
184        self.text = text;
185        self
186    }
187
188    pub fn disabled(mut self, disabled: bool) -> MenuBuilder<'a> {
189        self.disabled = disabled;
190        self
191    }
192
193    pub fn popup(mut self, popup: bool) -> MenuBuilder<'a> {
194        self.popup = popup;
195        self
196    }
197
198    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> MenuBuilder<'a> {
199        self.parent = Some(p.into());
200        self
201    }
202
203    pub fn build(self, menu: &mut Menu) -> Result<(), NwgError> {
204        if self.parent.is_none() {
205            return Err(NwgError::no_parent_menu());
206        }
207
208        menu.handle = ControlBase::build_hmenu()
209            .text(self.text)
210            .item(false)
211            .popup(self.popup)
212            .parent(self.parent.unwrap())
213            .build()?;
214
215        if self.disabled {
216            menu.set_enabled(false)
217        }
218
219        Ok(())
220    }
221}
222
223
224/** 
225    A windows menu item. Can be added to a menubar or another menu.
226
227    Requires the `menu` feature. 
228
229   **Builder parameters:**
230      - text: The text of the menu, including access key and shortcut label
231      - disabled: If the item can be selected by the user
232      - check: If the item should have a check mark next to it.
233      - parent: A top level window or a menu. With a top level window, the menu item is added to the menu bar.
234
235   **Control events:**
236      - OnMenuItemSelected: When a menu item is selected. This can be done by clicking or using the hot-key.
237      - OnMenuHover: When the user hovers the menu
238
239
240    **Menu Access Keys**
241
242    Just like Menus, menu items can have access keys. An access key is an underlined letter in the text of a menu item.
243    When a menu is active, the user can select a menu item by pressing the key that corresponds to the item's underlined letter.
244    The user makes the menu bar active by pressing the ALT key to highlight the first item on the menu bar.
245    A menu is active when it is displayed.
246
247    To create an access key for a menu item, precede any character in the item's text string with an ampersand.
248    For example, the text string "&Move" causes the system to underline the letter "M".
249
250    ```rust
251    use native_windows_gui as nwg;
252
253    fn menu_item(item: &mut nwg::MenuItem, menu: &nwg::Menu) -> Result<(), nwg::NwgError> {
254        nwg::MenuItem::builder()
255            .text("&Hello")
256            .disabled(true)
257            .parent(menu)
258            .build(item)
259    }
260    ```
261
262    **Shortcut Label**
263
264    A shortcut label like "Ctrl+O" can be added with the `text` field. By prefixing the shortcut with a tab character `\t` and
265    adding it as a suffix to the text label, it will be rendered right-aligned in the menu item.
266
267    For example, an "Exit" menu item could have both an access key "E" and a shortcut label "Alt+F4" with `text: "&Exit\tAlt+F4"`.
268
269    **note:** This will only add a text label to the menu item, the keyboard handling must be done through other means.
270
271    ```rust
272    use native_windows_gui as nwg;
273
274    fn menu(menu: &mut nwg::Menu, window: &nwg::Window) -> Result<(), nwg::NwgError> {
275        nwg::Menu::builder()
276            .text("&Exit\tAlt+F4")
277            .disabled(false)
278            .parent(window)
279            .build(menu)
280    }
281    ```
282*/
283#[derive(Default, Debug, PartialEq, Eq)]
284pub struct MenuItem {
285    pub handle: ControlHandle
286}
287
288impl MenuItem {
289
290    pub fn builder<'a>() -> MenuItemBuilder<'a> {
291        MenuItemBuilder {
292            text: "Menu Item",
293            disabled: false,
294            check: false,
295            parent: None
296        }
297    }
298
299    /// Return true if the control user can interact with the control, return false otherwise
300    pub fn enabled(&self) -> bool {
301        if self.handle.blank() { panic!("{}", NOT_BOUND); }
302        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
303        
304        unsafe { mh::is_menuitem_enabled(parent_handle, None, Some(id)) }
305    }
306
307    /// Enable or disable the control
308    pub fn set_enabled(&self, v: bool) {
309        if self.handle.blank() { panic!("{}", NOT_BOUND); }
310        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
311
312        unsafe { mh::enable_menuitem(parent_handle, None, Some(id), v); }
313    }
314
315    /// Sets the check state of a menu item
316    pub fn set_checked(&self, check: bool) {
317        if self.handle.blank() { panic!("{}", NOT_BOUND); }
318        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
319
320        unsafe { mh::check_menu_item(parent_handle, id, check); }
321    }
322
323    /// Returns the check state of a menu item
324    pub fn checked(&self) -> bool {
325        if self.handle.blank() { panic!("{}", NOT_BOUND); }
326        let (parent_handle, id) = self.handle.hmenu_item().expect(BAD_HANDLE);
327
328        unsafe { mh::menu_item_checked(parent_handle, id) }
329    }
330
331}
332
333impl Drop for MenuItem {
334    fn drop(&mut self) {
335        self.handle.destroy();
336    }
337}
338
339pub struct MenuItemBuilder<'a> {
340    text: &'a str,
341    disabled: bool,
342    check: bool,
343    parent: Option<ControlHandle>
344}
345
346impl<'a> MenuItemBuilder<'a> {
347
348    pub fn text(mut self, text: &'a str) -> MenuItemBuilder<'a> {
349        self.text = text;
350        self
351    }
352
353    pub fn disabled(mut self, disabled: bool) -> MenuItemBuilder<'a> {
354        self.disabled = disabled;
355        self
356    }
357
358    pub fn check(mut self, check: bool) -> MenuItemBuilder<'a> {
359        self.check = check;
360        self
361    }
362
363    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> MenuItemBuilder<'a> {
364        self.parent = Some(p.into());
365        self
366    }
367
368    pub fn build(self, item: &mut MenuItem) -> Result<(), NwgError> {
369        if self.parent.is_none() {
370            return Err(NwgError::no_parent_menu());
371        }
372
373        item.handle = ControlBase::build_hmenu()
374            .text(self.text)
375            .item(true)
376            .parent(self.parent.unwrap())
377            .build()?;
378
379        if self.disabled {
380            item.set_enabled(false);
381        }
382
383        if self.check {
384            item.set_checked(true);
385        }
386
387        Ok(())
388    }
389}
390
391/**
392    A menu separator. Can be added between two menu item to separte them. Cannot be added to a menubar.
393
394    Requires the `menu` feature. 
395
396    **Builder parameters:**
397      - parent: A top level window or a menu. With a top level window, the menu item is added to the menu bar.
398
399   **Control events:**
400      - OnMenuHover: When the user hovers the menu
401
402    ```rust
403    use native_windows_gui as nwg;
404
405    fn separator(sep: &mut nwg::MenuSeparator, menu: &nwg::Menu) -> Result<(), nwg::NwgError> {
406        nwg::MenuSeparator::builder()
407            .parent(menu)
408            .build(sep)
409    }
410    ```
411*/
412#[derive(Default, Debug, PartialEq, Eq)]
413pub struct MenuSeparator {
414    pub handle: ControlHandle
415}
416
417impl MenuSeparator {
418
419    pub fn builder() -> MenuSeparatorBuilder {
420        MenuSeparatorBuilder {
421            parent: None
422        }
423    }
424
425}
426
427pub struct MenuSeparatorBuilder {
428    parent: Option<ControlHandle>
429}
430
431impl MenuSeparatorBuilder {
432
433    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> MenuSeparatorBuilder {
434        self.parent = Some(p.into());
435        self
436    }
437
438    pub fn build(self, sep: &mut MenuSeparator) -> Result<(), NwgError> {
439        if self.parent.is_none() {
440            return Err(NwgError::no_parent_menu());
441        }
442
443        sep.handle = ControlBase::build_hmenu()
444            .separator(true)
445            .parent(self.parent.unwrap())
446            .build()?;
447
448        Ok(())
449    }
450}
451
452impl Drop for MenuSeparator {
453    fn drop(&mut self) {
454        self.handle.destroy();
455    }
456}