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 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14 pub struct MenuFlag: i32 {
15 const Normal = 0;
17 const Inactive = 1;
19 const Toggle = 2;
21 const Value = 4;
23 const Radio = 8;
25 const Invisible = 0x10;
27 const SubmenuPointer = 0x20;
29 const Submenu = 0x40;
31 const MenuDivider = 0x80;
33 const MenuHorizontal = 0x100;
35 }
36}
37
38#[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#[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#[repr(i32)]
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum MenuButtonType {
66 Popup1 = 1,
68 Popup2,
70 Popup12,
72 Popup3,
74 Popup13,
76 Popup23,
78 Popup123,
80}
81
82crate::macros::widget::impl_widget_type!(MenuButtonType);
83
84impl MenuButton {
85 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#[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#[repr(i32)]
146#[derive(Debug, Copy, Clone, PartialEq, Eq)]
147pub enum WindowMenuStyle {
148 NoWindowMenu = 0,
150 TabbingModeNone,
152 TabbingModeAutomatic,
154 TabbingModePreferred,
156}
157
158#[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 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 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#[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 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 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 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 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 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 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 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 pub fn label_type(&self) -> LabelType {
367 unsafe { mem::transmute(Fl_Menu_Item_label_type(*self.inner)) }
368 }
369
370 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 pub fn label_color(&self) -> Color {
379 unsafe { mem::transmute(Fl_Menu_Item_label_color(*self.inner)) }
380 }
381
382 pub fn set_label_color(&mut self, color: Color) {
384 unsafe { Fl_Menu_Item_set_label_color(*self.inner, color.bits()) }
385 }
386
387 pub fn label_font(&self) -> Font {
389 unsafe { mem::transmute(Fl_Menu_Item_label_font(*self.inner)) }
390 }
391
392 pub fn set_label_font(&mut self, font: Font) {
394 unsafe { Fl_Menu_Item_set_label_font(*self.inner, font.bits()) }
395 }
396
397 pub fn label_size(&self) -> i32 {
399 unsafe { Fl_Menu_Item_label_size(*self.inner) }
400 }
401
402 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 pub fn value(&self) -> bool {
410 unsafe { Fl_Menu_Item_value(*self.inner) != 0 }
411 }
412
413 pub fn set(&mut self) {
415 unsafe { Fl_Menu_Item_set(*self.inner) }
416 }
417
418 pub fn clear(&mut self) {
420 unsafe { Fl_Menu_Item_clear(*self.inner) }
421 }
422
423 pub fn visible(&self) -> bool {
425 unsafe { Fl_Menu_Item_visible(*self.inner) != 0 }
426 }
427
428 pub fn active(&self) -> bool {
430 unsafe { Fl_Menu_Item_active(*self.inner) != 0 }
431 }
432
433 pub fn activate(&mut self) {
435 unsafe { Fl_Menu_Item_activate(*self.inner) }
436 }
437
438 pub fn deactivate(&mut self) {
440 unsafe { Fl_Menu_Item_deactivate(*self.inner) }
441 }
442
443 pub fn is_submenu(&self) -> bool {
445 unsafe { Fl_Menu_Item_submenu(*self.inner) != 0 }
446 }
447
448 pub fn is_checkbox(&self) -> bool {
450 unsafe { Fl_Menu_Item_checkbox(*self.inner) != 0 }
451 }
452
453 pub fn is_radio(&self) -> bool {
455 unsafe { Fl_Menu_Item_radio(*self.inner) != 0 }
456 }
457
458 pub fn show(&mut self) {
460 unsafe { Fl_Menu_Item_show(*self.inner) }
461 }
462
463 pub fn hide(&mut self) {
465 unsafe { Fl_Menu_Item_hide(*self.inner) }
466 }
467
468 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 pub fn children(&self) -> i32 {
485 unsafe { Fl_Menu_Item_children(*self.inner) }
486 }
487
488 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 pub fn size(&self) -> i32 {
499 unsafe { Fl_Menu_Item_children(*self.inner) }
500 }
501
502 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 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 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 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 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 pub fn was_deleted(&self) -> bool {
570 self.inner.is_null()
571 }
572
573 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 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 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 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 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 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 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 pub fn set_shortcut(&mut self, shortcut: Shortcut) {
740 unsafe {
741 Fl_Menu_Item_set_shortcut(*self.inner, shortcut.bits());
742 }
743 }
744
745 pub fn set_flag(&mut self, flag: MenuFlag) {
747 unsafe {
748 Fl_Menu_Item_set_flag(*self.inner, flag.bits());
749 }
750 }
751}
752
753pub unsafe fn delete_menu_item(item: &MenuItem) {
757 unsafe { Fl_Menu_Item_delete(*item.inner) }
758}
759
760pub 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#[derive(Debug, Clone, Copy)]
780pub struct MacAppMenu;
781
782impl MacAppMenu {
783 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 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 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 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 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 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 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 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 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 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}