fltk/
menu.rs

1use crate::enums::{Color, Font, LabelType, Shortcut};
2use crate::prelude::*;
3use crate::utils::FlString;
4use fltk_sys::menu::*;
5use std::{
6    ffi::{CStr, CString},
7    mem,
8    os::raw,
9};
10
11bitflags::bitflags! {
12    /// Defines the menu flag for any added menu items using the add() method
13    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14    pub struct MenuFlag: i32 {
15        /// Normal item
16        const Normal = 0;
17        /// Inactive item
18        const Inactive = 1;
19        /// Item is a checkbox toggle (shows checkbox for on/off state)
20        const Toggle = 2;
21        /// The on/off state for checkbox/radio buttons (if set, state is 'on')
22        const Value = 4;
23        /// Item is a radio button
24        const Radio = 8;
25        /// Invisible item
26        const Invisible = 0x10;
27        /// Indicates user_data() is a pointer to another menu array (unused with Rust)
28        const SubmenuPointer = 0x20;
29        /// Menu item is a submenu
30        const Submenu = 0x40;
31        /// Menu divider
32        const MenuDivider = 0x80;
33        /// Horizontal menu (actually reserved for future use)
34        const MenuHorizontal = 0x100;
35    }
36}
37
38/// Creates a menu bar
39#[derive(Debug)]
40pub struct MenuBar {
41    inner: crate::widget::WidgetTracker,
42    is_derived: bool,
43}
44
45crate::macros::widget::impl_widget_ext!(MenuBar, Fl_Menu_Bar);
46crate::macros::widget::impl_widget_base!(MenuBar, Fl_Menu_Bar);
47crate::macros::widget::impl_widget_default!(MenuBar, Fl_Menu_Bar);
48crate::macros::menu::impl_menu_ext!(MenuBar, Fl_Menu_Bar);
49
50/// Creates a menu button
51#[derive(Debug)]
52pub struct MenuButton {
53    inner: crate::widget::WidgetTracker,
54    is_derived: bool,
55}
56
57crate::macros::widget::impl_widget_ext!(MenuButton, Fl_Menu_Button);
58crate::macros::widget::impl_widget_base!(MenuButton, Fl_Menu_Button);
59crate::macros::widget::impl_widget_default!(MenuButton, Fl_Menu_Button);
60crate::macros::menu::impl_menu_ext!(MenuButton, Fl_Menu_Button);
61
62/// Defines the menu button types, which can be changed dynamically using the `set_type()`.
63#[repr(i32)]
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum MenuButtonType {
66    /// pops up with the mouse 1st button.
67    Popup1 = 1,
68    /// pops up with the mouse 2nd button.
69    Popup2,
70    /// pops up with the mouse 1st or 2nd buttons.
71    Popup12,
72    /// pops up with the mouse 3rd button.
73    Popup3,
74    /// pops up with the mouse 1st or 3rd buttons.
75    Popup13,
76    /// pops up with the mouse 2nd or 3rd buttons.
77    Popup23,
78    /// pops up with any mouse button.
79    Popup123,
80}
81
82crate::macros::widget::impl_widget_type!(MenuButtonType);
83
84impl MenuButton {
85    /// Act exactly as though the user clicked the button or typed the shortcut key
86    pub fn popup(&self) -> Option<MenuItem> {
87        if self.size() == 0 {
88            return None;
89        }
90        unsafe {
91            let ptr = Fl_Menu_Button_popup(self.inner.widget() as _);
92            if ptr.is_null() {
93                None
94            } else {
95                let item = MenuItem::from_ptr(ptr as *mut Fl_Menu_Item);
96                Some(item)
97            }
98        }
99    }
100}
101
102/// Creates a menu choice
103///
104/// The [`set_frame`](crate::prelude::WidgetExt::set_frame) method styles the
105/// dropdown menu. `Choice` does not expose it's uderlying widget (a
106/// [`DownBox`](crate::enums::FrameType::DownBox)). It can only be changed
107/// via the app scheme or by globally changin the draw function of
108/// [`DownBox`](crate::enums::FrameType::DownBox):
109///
110/// ```rust,no_run
111///use fltk::{enums::*, prelude::*, *};
112///
113///fn my_down_box(x: i32, y: i32, w: i32, h: i32, col: Color) {
114///    draw::draw_rect_fill(x, y, w, h, Color::Red);
115///    draw::draw_rect_fill(x + 1, y + 1, w - 2, h - 2, Color::Background2); // change values to change thickness
116///}
117///
118///fn main() {
119///     let a = app::App::default();
120///     app::set_frame_type_cb(FrameType::DownBox, my_down_box, 0, 0, 0, 0);
121///     let mut win = window::Window::new(100, 100, 400, 300, None);
122///     win.set_color(Color::from_rgb(211, 211, 211));
123///     let mut inp = input::Input::new(50, 10, 100, 30, None); // would work for any widget which has a DownBox frame type
124///     let mut choice = menu::Choice::new(50, 100, 100, 30, None);
125///     choice.add_choice("Choice 1| Choice 2| choice 3");
126///     win.end();
127///     win.show();
128///     a.run().unwrap();
129///}
130///```
131///
132/// For more extensive options see the `custom_choice` example.
133#[derive(Debug)]
134pub struct Choice {
135    inner: crate::widget::WidgetTracker,
136    is_derived: bool,
137}
138
139crate::macros::widget::impl_widget_ext!(Choice, Fl_Choice);
140crate::macros::widget::impl_widget_base!(Choice, Fl_Choice);
141crate::macros::widget::impl_widget_default!(Choice, Fl_Choice);
142crate::macros::menu::impl_menu_ext!(Choice, Fl_Choice);
143
144/// Defines the window menu style for `SysMenuBar`
145#[repr(i32)]
146#[derive(Debug, Copy, Clone, PartialEq, Eq)]
147pub enum WindowMenuStyle {
148    /// No Window menu in the system menu bar
149    NoWindowMenu = 0,
150    /// No tabbed windows, but the system menu bar contains a Window menu
151    TabbingModeNone,
152    /// Windows are created by themselves but can be tabbed later
153    TabbingModeAutomatic,
154    /// Windows are tabbed when created
155    TabbingModePreferred,
156}
157
158/// Creates a macOS system menu bar on macOS and a normal menu bar on other systems
159#[derive(Debug)]
160pub struct SysMenuBar {
161    inner: crate::widget::WidgetTracker,
162    is_derived: bool,
163}
164
165crate::macros::widget::impl_widget_ext!(SysMenuBar, Fl_Sys_Menu_Bar);
166crate::macros::widget::impl_widget_base!(SysMenuBar, Fl_Sys_Menu_Bar);
167crate::macros::widget::impl_widget_default!(SysMenuBar, Fl_Sys_Menu_Bar);
168crate::macros::menu::impl_menu_ext!(SysMenuBar, Fl_Sys_Menu_Bar);
169
170impl SysMenuBar {
171    /// Sets the macos window menu style
172    pub fn set_window_menu_style(style: WindowMenuStyle) {
173        unsafe {
174            Fl_Sys_Menu_Bar_set_window_menu_style(style as i32);
175        }
176    }
177
178    /// Sets the about menu item callback
179    pub fn set_about_callback<F: FnMut(&mut Self) + 'static>(&mut self, cb: F) {
180        unsafe {
181            unsafe extern "C" fn shim(wid: *mut Fl_Widget, data: *mut std::os::raw::c_void) {
182                unsafe {
183                    let mut wid = SysMenuBar::from_widget_ptr(wid as *mut _);
184                    let a = data as *mut Box<dyn FnMut(&mut SysMenuBar)>;
185                    let f: &mut (dyn FnMut(&mut SysMenuBar)) = &mut **a;
186                    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut wid)));
187                }
188            }
189            let a: *mut Box<dyn FnMut(&mut Self)> = Box::into_raw(Box::new(Box::new(cb)));
190            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
191            let callback: Fl_Callback = Some(shim);
192            Fl_Sys_Menu_Bar_about(self.inner.widget() as _, callback, data);
193        }
194    }
195}
196
197type MenuItemWrapper = std::rc::Rc<*mut Fl_Menu_Item>;
198
199/// Creates a menu item
200#[derive(Debug, Clone)]
201pub struct MenuItem {
202    inner: MenuItemWrapper,
203}
204
205impl Drop for MenuItem {
206    fn drop(&mut self) {
207        assert!(!self.inner.is_null());
208        if MenuItemWrapper::strong_count(&self.inner) == 1 {
209            unsafe {
210                Fl_Menu_Item_delete(*self.inner);
211            }
212        }
213    }
214}
215
216#[cfg(not(feature = "single-threaded"))]
217unsafe impl Send for MenuItem {}
218
219#[cfg(not(feature = "single-threaded"))]
220unsafe impl Sync for MenuItem {}
221
222impl PartialEq for MenuItem {
223    fn eq(&self, other: &Self) -> bool {
224        *self.inner == *other.inner
225    }
226}
227
228impl Eq for MenuItem {}
229
230impl IntoIterator for MenuItem {
231    type Item = MenuItem;
232    type IntoIter = std::vec::IntoIter<Self::Item>;
233
234    fn into_iter(self) -> Self::IntoIter {
235        let mut v: Vec<MenuItem> = vec![];
236        let mut i = 0;
237        while let Some(item) = self.at(i) {
238            v.push(item);
239            i += 1;
240        }
241        v.into_iter()
242    }
243}
244
245impl MenuItem {
246    /// Initializes a `MenuItem` from a pointer
247    /// # Safety
248    /// The pointer must be valid
249    pub unsafe fn from_ptr(ptr: *mut Fl_Menu_Item) -> MenuItem {
250        unsafe {
251            assert!(!ptr.is_null());
252            let inner = MenuItemWrapper::from(ptr);
253            let ptr = MenuItemWrapper::into_raw(inner);
254            MenuItemWrapper::increment_strong_count(ptr);
255            let inner = MenuItemWrapper::from_raw(ptr);
256            MenuItem { inner }
257        }
258    }
259
260    /// Returns the inner pointer from a `MenuItem`
261    /// # Safety
262    /// Can return multiple mutable pointers to the same item
263    pub unsafe fn as_ptr(&self) -> *mut Fl_Menu_Item {
264        unsafe {
265            let ptr = MenuItemWrapper::into_raw(MenuItemWrapper::clone(&self.inner));
266            MenuItemWrapper::increment_strong_count(ptr);
267            let inner = MenuItemWrapper::from_raw(ptr);
268            *inner
269        }
270    }
271
272    /// Initializes a new menu item.
273    /// This will allocate a static `MenuItem`, that is expected to live for the entirety of the program.
274    pub fn new(choices: &[&'static str]) -> MenuItem {
275        unsafe {
276            let sz = choices.len();
277            let mut temp: Vec<*mut raw::c_char> = vec![];
278            for &choice in choices {
279                let c = CString::safe_new(choice);
280                temp.push(c.into_raw() as _);
281            }
282            let item_ptr = Fl_Menu_Item_new(temp.as_ptr() as *mut *mut raw::c_char, sz as i32);
283            assert!(!item_ptr.is_null());
284            MenuItem {
285                inner: MenuItemWrapper::new(item_ptr),
286            }
287        }
288    }
289
290    /// Creates a popup menu at the specified coordinates and returns its choice
291    pub fn popup(&self, x: i32, y: i32) -> Option<MenuItem> {
292        if self.size() == 0 {
293            return None;
294        }
295        unsafe {
296            let item = Fl_Menu_Item_popup(*self.inner, x, y);
297            if item.is_null() {
298                None
299            } else {
300                let item = MenuItem::from_ptr(item as *mut Fl_Menu_Item);
301                Some(item)
302            }
303        }
304    }
305
306    #[allow(clippy::too_many_arguments)]
307    /// Creates a pulldown menu at the specified coordinates and returns its choice
308    pub fn pulldown(
309        &self,
310        x: i32,
311        y: i32,
312        w: i32,
313        h: i32,
314        picked: Option<MenuItem>,
315        menu: Option<&impl MenuExt>,
316    ) -> Option<MenuItem> {
317        if self.size() == 0 {
318            return None;
319        }
320        unsafe {
321            let picked = if let Some(m) = picked {
322                *m.inner as _
323            } else {
324                std::ptr::null()
325            };
326            let title = std::ptr::null();
327            let menu = if let Some(m) = menu {
328                m.as_widget_ptr() as _
329            } else {
330                std::ptr::null()
331            };
332            let item = Fl_Menu_Item_pulldown(*self.inner, x, y, w, h, picked, menu, title, 0);
333            if item.is_null() {
334                None
335            } else {
336                let item = MenuItem::from_ptr(item as *mut Fl_Menu_Item);
337                Some(item)
338            }
339        }
340    }
341
342    /// Returns the label of the menu item
343    pub fn label(&self) -> Option<String> {
344        unsafe {
345            let label_ptr = Fl_Menu_Item_label(*self.inner);
346            if label_ptr.is_null() {
347                return None;
348            }
349            Some(
350                CStr::from_ptr(label_ptr as *mut raw::c_char)
351                    .to_string_lossy()
352                    .to_string(),
353            )
354        }
355    }
356
357    /// Sets the label of the menu item
358    pub fn set_label(&mut self, txt: &str) {
359        unsafe {
360            let txt = CString::safe_new(txt);
361            Fl_Menu_Item_set_label(*self.inner, txt.into_raw() as _);
362        }
363    }
364
365    /// Returns the label type of the menu item
366    pub fn label_type(&self) -> LabelType {
367        unsafe { mem::transmute(Fl_Menu_Item_label_type(*self.inner)) }
368    }
369
370    /// Sets the label type of the menu item
371    pub fn set_label_type(&mut self, typ: LabelType) {
372        unsafe {
373            Fl_Menu_Item_set_label_type(*self.inner, typ as i32);
374        }
375    }
376
377    /// Returns the label color of the menu item
378    pub fn label_color(&self) -> Color {
379        unsafe { mem::transmute(Fl_Menu_Item_label_color(*self.inner)) }
380    }
381
382    /// Sets the label color of the menu item
383    pub fn set_label_color(&mut self, color: Color) {
384        unsafe { Fl_Menu_Item_set_label_color(*self.inner, color.bits()) }
385    }
386
387    /// Returns the label font of the menu item
388    pub fn label_font(&self) -> Font {
389        unsafe { mem::transmute(Fl_Menu_Item_label_font(*self.inner)) }
390    }
391
392    /// Sets the label font of the menu item
393    pub fn set_label_font(&mut self, font: Font) {
394        unsafe { Fl_Menu_Item_set_label_font(*self.inner, font.bits()) }
395    }
396
397    /// Returns the label size of the menu item
398    pub fn label_size(&self) -> i32 {
399        unsafe { Fl_Menu_Item_label_size(*self.inner) }
400    }
401
402    /// Sets the label size of the menu item
403    pub fn set_label_size(&mut self, sz: i32) {
404        let sz = if sz < 1 { 1 } else { sz };
405        unsafe { Fl_Menu_Item_set_label_size(*self.inner, sz) }
406    }
407
408    /// Returns the value of the menu item
409    pub fn value(&self) -> bool {
410        unsafe { Fl_Menu_Item_value(*self.inner) != 0 }
411    }
412
413    /// Sets the menu item
414    pub fn set(&mut self) {
415        unsafe { Fl_Menu_Item_set(*self.inner) }
416    }
417
418    /// Turns the check or radio item "off" for the menu item
419    pub fn clear(&mut self) {
420        unsafe { Fl_Menu_Item_clear(*self.inner) }
421    }
422
423    /// Returns whether the menu item is visible or not
424    pub fn visible(&self) -> bool {
425        unsafe { Fl_Menu_Item_visible(*self.inner) != 0 }
426    }
427
428    /// Returns whether the menu item is active
429    pub fn active(&self) -> bool {
430        unsafe { Fl_Menu_Item_active(*self.inner) != 0 }
431    }
432
433    /// Activates the menu item
434    pub fn activate(&mut self) {
435        unsafe { Fl_Menu_Item_activate(*self.inner) }
436    }
437
438    /// Deactivates the menu item
439    pub fn deactivate(&mut self) {
440        unsafe { Fl_Menu_Item_deactivate(*self.inner) }
441    }
442
443    /// Returns whether a menu item is a submenu
444    pub fn is_submenu(&self) -> bool {
445        unsafe { Fl_Menu_Item_submenu(*self.inner) != 0 }
446    }
447
448    /// Returns whether a menu item is a checkbox
449    pub fn is_checkbox(&self) -> bool {
450        unsafe { Fl_Menu_Item_checkbox(*self.inner) != 0 }
451    }
452
453    /// Returns whether a menu item is a radio item
454    pub fn is_radio(&self) -> bool {
455        unsafe { Fl_Menu_Item_radio(*self.inner) != 0 }
456    }
457
458    /// Shows the menu item
459    pub fn show(&mut self) {
460        unsafe { Fl_Menu_Item_show(*self.inner) }
461    }
462
463    /// Hides the menu item
464    pub fn hide(&mut self) {
465        unsafe { Fl_Menu_Item_hide(*self.inner) }
466    }
467
468    /// Get the next menu item skipping submenus
469    pub fn next(&self, idx: i32) -> Option<MenuItem> {
470        unsafe {
471            let ptr = Fl_Menu_Item_next(*self.inner, idx);
472            if ptr.is_null() {
473                return None;
474            }
475            let label_ptr = Fl_Menu_Item_label(ptr);
476            if label_ptr.is_null() {
477                return None;
478            }
479            Some(MenuItem::from_ptr(ptr))
480        }
481    }
482
483    /// Get children of `MenuItem`
484    pub fn children(&self) -> i32 {
485        unsafe { Fl_Menu_Item_children(*self.inner) }
486    }
487
488    /// Get the submenu count
489    pub fn submenus(&self) -> i32 {
490        let mut i = 0;
491        while let Some(_item) = self.next(i) {
492            i += 1;
493        }
494        i
495    }
496
497    /// Get the size of the `MenuItem`
498    pub fn size(&self) -> i32 {
499        unsafe { Fl_Menu_Item_children(*self.inner) }
500    }
501
502    /// Get the menu item at `idx`
503    pub fn at(&self, idx: i32) -> Option<MenuItem> {
504        assert!(idx < self.size());
505        unsafe {
506            let ptr = Fl_Menu_Item_at(*self.inner, idx);
507            if ptr.is_null() {
508                None
509            } else {
510                Some(MenuItem::from_ptr(ptr as _))
511            }
512        }
513    }
514
515    /// Get the user data
516    /// # Safety
517    /// Can return multiple mutable instances of the user data, which has a different lifetime than the object
518    pub unsafe fn user_data(&self) -> Option<Box<dyn FnMut()>> {
519        unsafe {
520            let ptr = Fl_Menu_Item_user_data(*self.inner);
521            if ptr.is_null() {
522                None
523            } else {
524                let x = ptr as *mut Box<dyn FnMut()>;
525                let x = Box::from_raw(x);
526                Fl_Menu_Item_set_callback(*self.inner, None, std::ptr::null_mut());
527                Some(*x)
528            }
529        }
530    }
531
532    /// Set a callback for the menu item
533    pub fn set_callback<F: FnMut(&mut Choice) + 'static>(&mut self, cb: F) {
534        unsafe {
535            unsafe extern "C" fn shim(wid: *mut fltk_sys::menu::Fl_Widget, data: *mut raw::c_void) {
536                unsafe {
537                    let mut wid = crate::widget::Widget::from_widget_ptr(wid as *mut _);
538                    let a: *mut Box<dyn FnMut(&mut crate::widget::Widget)> =
539                        data as *mut Box<dyn FnMut(&mut crate::widget::Widget)>;
540                    let f: &mut (dyn FnMut(&mut crate::widget::Widget)) = &mut **a;
541                    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut wid)));
542                }
543            }
544            let _old_data = self.user_data();
545            let a: *mut Box<dyn FnMut(&mut Choice)> = Box::into_raw(Box::new(Box::new(cb)));
546            let data: *mut raw::c_void = a as *mut std::ffi::c_void;
547            let callback: fltk_sys::menu::Fl_Callback = Some(shim);
548            Fl_Menu_Item_set_callback(*self.inner, callback, data);
549        }
550    }
551
552    /// Run the menu item's callback
553    pub fn do_callback<W: MenuExt>(&mut self, w: &W) {
554        unsafe {
555            Fl_Menu_Item_do_callback(*self.inner, w.as_widget_ptr() as _);
556        }
557    }
558
559    /// Use a sender to send a message during callback
560    pub fn emit<T: 'static + Clone + Send + Sync>(
561        &mut self,
562        sender: crate::app::Sender<T>,
563        msg: T,
564    ) {
565        self.set_callback(move |_| sender.send(msg.clone()));
566    }
567
568    /// Check if a menu item was deleted
569    pub fn was_deleted(&self) -> bool {
570        self.inner.is_null()
571    }
572
573    /// Draw a box around the menu item.
574    /// Requires the call to be made inside a MenuExt-implementing widget's own draw method
575    pub fn draw<M: MenuExt>(&self, x: i32, y: i32, w: i32, h: i32, menu: &M, selected: bool) {
576        unsafe {
577            Fl_Menu_Item_draw(
578                *self.inner,
579                x,
580                y,
581                w,
582                h,
583                menu.as_widget_ptr() as _,
584                i32::from(selected),
585            );
586        }
587    }
588
589    /// Measure the width and height of a menu item
590    pub fn measure(&self) -> (i32, i32) {
591        let mut h = 0;
592        let ret = unsafe {
593            Fl_Menu_Item_measure(*self.inner, std::ptr::from_mut(&mut h), std::ptr::null())
594        };
595        (ret, h)
596    }
597
598    /**
599        Add an image to a menu item
600        ```rust,no_run
601        use fltk::{prelude::*, *};
602        const PXM: &[&str] = &[
603            "13 11 3 1",
604            "   c None",
605            "x  c #d8d833",
606            "@  c #808011",
607            "             ",
608            "     @@@@    ",
609            "    @xxxx@   ",
610            "@@@@@xxxx@@  ",
611            "@xxxxxxxxx@  ",
612            "@xxxxxxxxx@  ",
613            "@xxxxxxxxx@  ",
614            "@xxxxxxxxx@  ",
615            "@xxxxxxxxx@  ",
616            "@xxxxxxxxx@  ",
617            "@@@@@@@@@@@  "
618        ];
619        let image = image::Pixmap::new(PXM).unwrap();
620        let mut menu = menu::MenuBar::default();
621        menu.add(
622            "&File/Open...\t",
623            enums::Shortcut::Ctrl | 'o',
624            menu::MenuFlag::Normal,
625            |_| println!("Opened file!"),
626        );
627        if let Some(mut item) = menu.find_item("&File/Open...\t") {
628            item.add_image(Some(image), true);
629        }
630        ```
631    */
632    pub fn add_image<I: ImageExt>(&mut self, image: Option<I>, on_left: bool) {
633        unsafe {
634            if let Some(image) = image {
635                assert!(!image.was_deleted());
636                Fl_Menu_Item_add_image(*self.inner, image.as_image_ptr() as _, i32::from(on_left));
637            } else {
638                Fl_Menu_Item_add_image(*self.inner, std::ptr::null_mut(), i32::from(on_left));
639            }
640        }
641    }
642
643    /// Add a menu item
644    pub fn add<F: FnMut(&mut Choice) + 'static>(
645        &mut self,
646        name: &str,
647        shortcut: Shortcut,
648        flag: MenuFlag,
649        cb: F,
650    ) -> i32 {
651        let temp = CString::safe_new(name);
652        unsafe {
653            unsafe extern "C" fn shim(wid: *mut Fl_Widget, data: *mut std::os::raw::c_void) {
654                unsafe {
655                    let mut wid = crate::widget::Widget::from_widget_ptr(wid as *mut _);
656                    let a: *mut Box<dyn FnMut(&mut crate::widget::Widget)> =
657                        data as *mut Box<dyn FnMut(&mut crate::widget::Widget)>;
658                    let f: &mut (dyn FnMut(&mut crate::widget::Widget)) = &mut **a;
659                    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut wid)));
660                }
661            }
662            let a: *mut Box<dyn FnMut(&mut Choice)> = Box::into_raw(Box::new(Box::new(cb)));
663            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
664            let callback: Fl_Callback = Some(shim);
665            Fl_Menu_Item_add(
666                *self.inner,
667                temp.as_ptr(),
668                shortcut.bits(),
669                callback,
670                data,
671                flag.bits(),
672            )
673        }
674    }
675
676    /// Insert a menu item
677    pub fn insert<F: FnMut(&mut Choice) + 'static>(
678        &mut self,
679        idx: i32,
680        name: &str,
681        shortcut: Shortcut,
682        flag: MenuFlag,
683        cb: F,
684    ) -> i32 {
685        let temp = CString::safe_new(name);
686        unsafe {
687            unsafe extern "C" fn shim(wid: *mut Fl_Widget, data: *mut std::os::raw::c_void) {
688                unsafe {
689                    let mut wid = crate::widget::Widget::from_widget_ptr(wid as *mut _);
690                    let a: *mut Box<dyn FnMut(&mut crate::widget::Widget)> =
691                        data as *mut Box<dyn FnMut(&mut crate::widget::Widget)>;
692                    let f: &mut (dyn FnMut(&mut crate::widget::Widget)) = &mut **a;
693                    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut wid)));
694                }
695            }
696            let a: *mut Box<dyn FnMut(&mut Choice)> = Box::into_raw(Box::new(Box::new(cb)));
697            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
698            let callback: Fl_Callback = Some(shim);
699            Fl_Menu_Item_insert(
700                *self.inner,
701                idx,
702                temp.as_ptr(),
703                shortcut.bits(),
704                callback,
705                data,
706                flag.bits(),
707            )
708        }
709    }
710
711    /// Add a menu item along with an emit (sender and message).
712    pub fn add_emit<T: 'static + Clone + Send + Sync>(
713        &mut self,
714        label: &str,
715        shortcut: Shortcut,
716        flag: MenuFlag,
717        sender: crate::app::Sender<T>,
718        msg: T,
719    ) -> i32 {
720        self.add(label, shortcut, flag, move |_| sender.send(msg.clone()))
721    }
722
723    /// Insert a menu item along with an emit (sender and message).
724    pub fn insert_emit<T: 'static + Clone + Send + Sync>(
725        &mut self,
726        idx: i32,
727        label: &str,
728        shortcut: Shortcut,
729        flag: MenuFlag,
730        sender: crate::app::Sender<T>,
731        msg: T,
732    ) -> i32 {
733        self.insert(idx, label, shortcut, flag, move |_| {
734            sender.send(msg.clone());
735        })
736    }
737
738    /// Set the menu item's shortcut
739    pub fn set_shortcut(&mut self, shortcut: Shortcut) {
740        unsafe {
741            Fl_Menu_Item_set_shortcut(*self.inner, shortcut.bits());
742        }
743    }
744
745    /// Set the menu item's shortcut
746    pub fn set_flag(&mut self, flag: MenuFlag) {
747        unsafe {
748            Fl_Menu_Item_set_flag(*self.inner, flag.bits());
749        }
750    }
751}
752
753/// Delete a menu item
754/// # Safety
755/// The wrapper can't assure use after free when manually deleting a menu item
756pub unsafe fn delete_menu_item(item: &MenuItem) {
757    unsafe { Fl_Menu_Item_delete(*item.inner) }
758}
759
760/// Set a callback for the "About" item of the system application menu on macOS.
761pub fn mac_set_about<F: FnMut() + 'static>(cb: F) {
762    unsafe {
763        unsafe extern "C" fn shim(_wid: *mut fltk_sys::menu::Fl_Widget, data: *mut raw::c_void) {
764            unsafe {
765                let a: *mut Box<dyn FnMut()> = data as *mut Box<dyn FnMut()>;
766                let f: &mut (dyn FnMut()) = &mut **a;
767                let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
768            }
769        }
770        let a: *mut Box<dyn FnMut()> = Box::into_raw(Box::new(Box::new(cb)));
771        let data: *mut raw::c_void = a as *mut std::ffi::c_void;
772        let callback: fltk_sys::menu::Fl_Callback = Some(shim);
773        Fl_mac_set_about(callback, data, 0);
774    }
775}
776
777/// Wrapper around `Fl_Mac_App_Menu` which exposes several static methods
778/// allowing the customization of the default system menu bar for the fltk application
779#[derive(Debug, Clone, Copy)]
780pub struct MacAppMenu;
781
782impl MacAppMenu {
783    /// Sets the about text
784    pub fn set_about(about: &'static str) {
785        unsafe {
786            let about = CString::safe_new(about);
787            Fl_Mac_App_Menu_set_about(about.as_ptr());
788        }
789    }
790
791    /// Sets the print text
792    pub fn set_print(print: &'static str) {
793        unsafe {
794            let print = CString::safe_new(print);
795            Fl_Mac_App_Menu_set_print(print.as_ptr());
796        }
797    }
798
799    /// Sets the print no titlebar text
800    pub fn set_print_no_titlebar(print_no_titlebar: &'static str) {
801        unsafe {
802            let print_no_titlebar = CString::safe_new(print_no_titlebar);
803            Fl_Mac_App_Menu_set_print_no_titlebar(print_no_titlebar.as_ptr());
804        }
805    }
806
807    /// Sets the toggle print titlebar text
808    pub fn set_toggle_print_titlebar(toggle_print_titlebar: &'static str) {
809        unsafe {
810            let toggle_print_titlebar = CString::safe_new(toggle_print_titlebar);
811            Fl_Mac_App_Menu_set_toggle_print_titlebar(toggle_print_titlebar.as_ptr());
812        }
813    }
814
815    /// Sets the services text
816    pub fn set_services(services: &'static str) {
817        unsafe {
818            let services = CString::safe_new(services);
819            Fl_Mac_App_Menu_set_services(services.as_ptr());
820        }
821    }
822
823    /// Sets the hide text
824    pub fn set_hide(hide: &'static str) {
825        unsafe {
826            let hide = CString::safe_new(hide);
827            Fl_Mac_App_Menu_set_hide(hide.as_ptr());
828        }
829    }
830
831    /// Sets the hide others text
832    pub fn set_hide_others(hide_others: &'static str) {
833        unsafe {
834            let hide_others = CString::safe_new(hide_others);
835            Fl_Mac_App_Menu_set_hide_others(hide_others.as_ptr());
836        }
837    }
838
839    /// Sets the show text
840    pub fn set_show(show: &'static str) {
841        unsafe {
842            let show = CString::safe_new(show);
843            Fl_Mac_App_Menu_set_show(show.as_ptr());
844        }
845    }
846
847    /// Sets the quit text
848    pub fn set_quit(quit: &'static str) {
849        unsafe {
850            let quit = CString::safe_new(quit);
851            Fl_Mac_App_Menu_set_quit(quit.as_ptr());
852        }
853    }
854
855    /// Adds custom menu items to the application menu of the system menu bar.
856    /// They are positioned after the "Print Front Window / Toggle printing of titlebar" items,
857    /// or at their place if an item is removed by providing empty text
858    pub fn custom_application_menu_items(m: &MenuItem) {
859        unsafe {
860            Fl_Mac_App_Menu_custom_application_menu_items(m.as_ptr());
861        }
862    }
863}