pytauri_core/ext_mod_impl/
menu.rs

1use std::ops::Deref;
2
3use pyo3::{marker::Ungil, prelude::*, types::PyString};
4use pyo3_utils::{
5    py_wrapper::{PyWrapper, PyWrapperT0},
6    ungil::UnsafeUngilExt,
7};
8use tauri::menu::{self, ContextMenu as _, IsMenuItem, MenuId};
9
10use crate::{
11    ext_mod::{self, manager_method_impl, ImplManager, PyAppHandleExt as _},
12    tauri_runtime::Runtime,
13    utils::TauriError,
14};
15
16type TauriMenu = menu::Menu<Runtime>;
17type TauriMenuItem = menu::MenuItem<Runtime>;
18type TauriSubmenu = menu::Submenu<Runtime>;
19type TauriPredefinedMenuItem = menu::PredefinedMenuItem<Runtime>;
20type TauriCheckMenuItem = menu::CheckMenuItem<Runtime>;
21type TauriIconMenuItem = menu::IconMenuItem<Runtime>;
22type TauriMenuItemKind = menu::MenuItemKind<Runtime>;
23
24/// See also: [tauri::menu::MenuId].
25///
26/// Remember use [MenuID::intern] to create a new instance.
27pub type MenuID = PyString;
28/// See also: [tauri::menu::MenuEvent]
29///
30/// Remember use [MenuEvent::intern] to create a new instance.
31pub type MenuEvent = MenuID;
32pub use menu::{HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
33
34/// See also: [tauri::menu::MenuItemKind].
35#[derive(FromPyObject, IntoPyObject, IntoPyObjectRef)]
36pub enum MenuItemKind {
37    MenuItem(Py<MenuItem>),
38    Submenu(Py<Submenu>),
39    Predefined(Py<PredefinedMenuItem>),
40    Check(Py<CheckMenuItem>),
41    Icon(Py<IconMenuItem>),
42}
43
44impl MenuItemKind {
45    #[inline]
46    fn delegate_inner_ref<R>(&self, f: impl FnOnce(&dyn IsMenuItem<Runtime>) -> R) -> R {
47        match self {
48            MenuItemKind::MenuItem(v) => f(&*v.get().0.inner_ref()),
49            MenuItemKind::Submenu(v) => f(&*v.get().0.inner_ref()),
50            MenuItemKind::Predefined(v) => f(&*v.get().0.inner_ref()),
51            MenuItemKind::Check(v) => f(&*v.get().0.inner_ref()),
52            MenuItemKind::Icon(v) => f(&*v.get().0.inner_ref()),
53        }
54    }
55}
56
57trait TauriMenuProto {
58    fn append(&self, item: &dyn IsMenuItem<Runtime>) -> tauri::Result<()>;
59    fn prepend(&self, item: &dyn IsMenuItem<Runtime>) -> tauri::Result<()>;
60    fn insert(&self, item: &dyn IsMenuItem<Runtime>, position: usize) -> tauri::Result<()>;
61    fn remove(&self, item: &dyn IsMenuItem<Runtime>) -> tauri::Result<()>;
62}
63
64macro_rules! impl_tauri_menu_proto {
65    ($trait:ty => $($implementor:ty),* => $append:ident, $prepend:ident, $insert:ident, $remove:ident,) => {
66
67        $(
68            impl $trait for $implementor {
69                #[inline]
70                fn $append(&self, item: &dyn IsMenuItem<Runtime>) -> tauri::Result<()> {
71                    self.$append(item)
72                }
73
74                #[inline]
75                fn $prepend(&self, item: &dyn IsMenuItem<Runtime>) -> tauri::Result<()> {
76                    self.$prepend(item)
77                }
78
79                #[inline]
80                fn $insert(
81                    &self,
82                    item: &dyn IsMenuItem<Runtime>,
83                    position: usize,
84                ) -> tauri::Result<()> {
85                    self.$insert(item, position)
86                }
87
88                #[inline]
89                fn $remove(&self, item: &dyn IsMenuItem<Runtime>) -> tauri::Result<()> {
90                    self.$remove(item)
91                }
92            }
93        )*
94
95    };
96}
97
98impl_tauri_menu_proto!(TauriMenuProto => TauriMenu, TauriSubmenu => append, prepend, insert, remove,);
99
100impl MenuItemKind {
101    #[inline]
102    fn append_to_menu(&self, menu: &impl TauriMenuProto) -> tauri::Result<()> {
103        self.delegate_inner_ref(|item| menu.append(item))
104    }
105
106    #[inline]
107    fn append_items_to_menu<'a>(
108        items: impl Iterator<Item = &'a Self>,
109        menu: &impl TauriMenuProto,
110    ) -> tauri::Result<()> {
111        for item_kind in items {
112            item_kind.append_to_menu(menu)?;
113        }
114        Ok(())
115    }
116
117    #[inline]
118    fn prepend_to_menu(&self, menu: &impl TauriMenuProto) -> tauri::Result<()> {
119        self.delegate_inner_ref(|item| menu.prepend(item))
120    }
121
122    #[inline]
123    fn prepend_items_to_menu<'a>(
124        items: impl Iterator<Item = &'a Self>,
125        menu: &impl TauriMenuProto,
126    ) -> tauri::Result<()> {
127        for item_kind in items {
128            item_kind.prepend_to_menu(menu)?;
129        }
130        Ok(())
131    }
132
133    #[inline]
134    fn insert_to_menu(&self, menu: &impl TauriMenuProto, position: usize) -> tauri::Result<()> {
135        self.delegate_inner_ref(|item| menu.insert(item, position))
136    }
137
138    #[inline]
139    fn insert_items_to_menu<'a>(
140        items: impl Iterator<Item = &'a Self>,
141        menu: &impl TauriMenuProto,
142        position: usize,
143    ) -> tauri::Result<()> {
144        for (idx, item_kind) in items.enumerate() {
145            item_kind.insert_to_menu(menu, position + idx)?;
146        }
147        Ok(())
148    }
149
150    #[inline]
151    fn remove_from_menu(&self, menu: &impl TauriMenuProto) -> tauri::Result<()> {
152        self.delegate_inner_ref(|item| menu.remove(item))
153    }
154}
155
156impl MenuItemKind {
157    fn from_tauri(py: Python<'_>, menu_kind: TauriMenuItemKind) -> PyResult<Self> {
158        let menu_kind = match menu_kind {
159            TauriMenuItemKind::Submenu(submenu) => {
160                MenuItemKind::Submenu(Submenu::new(submenu).into_pyobject(py)?.unbind())
161            }
162            TauriMenuItemKind::MenuItem(menu_item) => {
163                MenuItemKind::MenuItem(MenuItem::new(menu_item).into_pyobject(py)?.unbind())
164            }
165            TauriMenuItemKind::Check(check_menu_item) => MenuItemKind::Check(
166                CheckMenuItem::new(check_menu_item)
167                    .into_pyobject(py)?
168                    .unbind(),
169            ),
170            TauriMenuItemKind::Predefined(predefined_menu_item) => MenuItemKind::Predefined(
171                PredefinedMenuItem::new(predefined_menu_item)
172                    .into_pyobject(py)?
173                    .unbind(),
174            ),
175            TauriMenuItemKind::Icon(icon_menu_item) => MenuItemKind::Icon(
176                IconMenuItem::new(icon_menu_item)
177                    .into_pyobject(py)?
178                    .unbind(),
179            ),
180        };
181        Ok(menu_kind)
182    }
183}
184
185/// See also: [tauri::menu::Menu]
186#[pyclass(frozen)]
187#[non_exhaustive]
188pub struct Menu(pub PyWrapper<PyWrapperT0<TauriMenu>>);
189
190impl Menu {
191    pub(crate) fn new(menu: TauriMenu) -> Self {
192        Self(PyWrapper::new0(menu))
193    }
194
195    #[inline]
196    fn new_impl(
197        py: Python<'_>,
198        manager: &impl tauri::Manager<Runtime>,
199        id: Option<impl Into<menu::MenuId> + Send>,
200        items: Option<Vec<MenuItemKind>>,
201    ) -> PyResult<Self> {
202        unsafe {
203            py.allow_threads_unsend(manager, |manager| {
204                let menu = if let Some(id) = id {
205                    TauriMenu::with_id(manager, id)
206                } else {
207                    TauriMenu::new(manager)
208                }?;
209
210                if let Some(items) = items {
211                    MenuItemKind::append_items_to_menu(items.iter(), &menu)?;
212                }
213                tauri::Result::Ok(Self::new(menu))
214            })
215        }
216        .map_err(TauriError::from)
217        .map_err(PyErr::from)
218    }
219}
220
221// All methods must release the GIL, because `menu` call `run_on_main_thread` internally, which may block.
222#[pymethods]
223impl Menu {
224    #[new]
225    fn __new__(py: Python<'_>, manager: ImplManager) -> PyResult<Self> {
226        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
227            py,
228            manager,
229            None::<&str>,
230            None
231        ))?
232    }
233
234    #[staticmethod]
235    fn with_id(py: Python<'_>, manager: ImplManager, id: String) -> PyResult<Self> {
236        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
237            py,
238            manager,
239            Some(MenuId(id)),
240            None
241        ))?
242    }
243
244    #[staticmethod]
245    fn with_items(
246        py: Python<'_>,
247        manager: ImplManager,
248        items: Vec<MenuItemKind>,
249    ) -> PyResult<Self> {
250        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
251            py,
252            manager,
253            None::<&str>,
254            Some(items)
255        ))?
256    }
257
258    #[staticmethod]
259    fn with_id_and_items(
260        py: Python<'_>,
261        manager: ImplManager,
262        id: String,
263        items: Vec<MenuItemKind>,
264    ) -> PyResult<Self> {
265        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
266            py,
267            manager,
268            Some(MenuId(id)),
269            Some(items)
270        ))?
271    }
272
273    #[staticmethod]
274    fn default(py: Python<'_>, app_handle: Py<ext_mod::AppHandle>) -> PyResult<Self> {
275        py.allow_threads(|| {
276            let app_handle = app_handle.get().0.inner_ref();
277            let menu = TauriMenu::default(app_handle.deref()).map_err(TauriError::from)?;
278            Ok(Menu::new(menu))
279        })
280    }
281
282    fn app_handle(&self, py: Python<'_>) -> Py<ext_mod::AppHandle> {
283        let menu = self.0.inner_ref();
284        // TODO, PERF: release the GIL?
285        let app_handle = menu.app_handle().py_app_handle().clone_ref(py);
286        app_handle
287    }
288
289    fn id<'py>(&self, py: Python<'py>) -> Bound<'py, MenuID> {
290        let menu = self.0.inner_ref();
291        MenuID::intern(py, &menu.id().0)
292    }
293
294    fn append(&self, py: Python<'_>, item: MenuItemKind) -> PyResult<()> {
295        py.allow_threads(|| {
296            let menu = self.0.inner_ref();
297            item.append_to_menu(menu.deref())
298                .map_err(TauriError::from)?;
299            Ok(())
300        })
301    }
302
303    fn append_items(&self, py: Python<'_>, items: Vec<MenuItemKind>) -> PyResult<()> {
304        py.allow_threads(|| {
305            let menu = self.0.inner_ref();
306            MenuItemKind::append_items_to_menu(items.iter(), menu.deref())
307                .map_err(TauriError::from)?;
308            Ok(())
309        })
310    }
311
312    fn prepend(&self, py: Python<'_>, item: MenuItemKind) -> PyResult<()> {
313        py.allow_threads(|| {
314            let menu = self.0.inner_ref();
315            item.prepend_to_menu(menu.deref())
316                .map_err(TauriError::from)?;
317            Ok(())
318        })
319    }
320
321    fn prepend_items(&self, py: Python<'_>, items: Vec<MenuItemKind>) -> PyResult<()> {
322        py.allow_threads(|| {
323            let menu = self.0.inner_ref();
324            MenuItemKind::prepend_items_to_menu(items.iter(), menu.deref())
325                .map_err(TauriError::from)?;
326            Ok(())
327        })
328    }
329
330    fn insert(&self, py: Python<'_>, item: MenuItemKind, position: usize) -> PyResult<()> {
331        py.allow_threads(|| {
332            let menu = self.0.inner_ref();
333            item.insert_to_menu(menu.deref(), position)
334                .map_err(TauriError::from)?;
335            Ok(())
336        })
337    }
338
339    fn insert_items(
340        &self,
341        py: Python<'_>,
342        items: Vec<MenuItemKind>,
343        position: usize,
344    ) -> PyResult<()> {
345        py.allow_threads(|| {
346            let menu = self.0.inner_ref();
347            MenuItemKind::insert_items_to_menu(items.iter(), menu.deref(), position)
348                .map_err(TauriError::from)?;
349            Ok(())
350        })
351    }
352
353    fn remove(&self, py: Python<'_>, item: MenuItemKind) -> PyResult<()> {
354        py.allow_threads(|| {
355            let menu = self.0.inner_ref();
356            item.remove_from_menu(menu.deref())
357                .map_err(TauriError::from)?;
358            Ok(())
359        })
360    }
361
362    fn remove_at(&self, py: Python<'_>, position: usize) -> PyResult<Option<MenuItemKind>> {
363        let item_kind = py.allow_threads(|| {
364            let menu = self.0.inner_ref();
365            menu.remove_at(position)
366                .map_err(TauriError::from)
367                .map_err(PyErr::from)
368        })?;
369
370        let item_kind = match item_kind {
371            Some(item_kind) => Some(MenuItemKind::from_tauri(py, item_kind)?),
372            None => None,
373        };
374
375        Ok(item_kind)
376    }
377
378    fn get(&self, py: Python<'_>, id: &str) -> PyResult<Option<MenuItemKind>> {
379        let item_kind = py.allow_threads(|| {
380            let menu = self.0.inner_ref();
381            menu.get(id)
382        });
383
384        let item_kind = match item_kind {
385            Some(item_kind) => Some(MenuItemKind::from_tauri(py, item_kind)?),
386            None => None,
387        };
388
389        Ok(item_kind)
390    }
391
392    fn items(&self, py: Python<'_>) -> PyResult<Vec<MenuItemKind>> {
393        let items = py.allow_threads(|| {
394            let menu = self.0.inner_ref();
395            menu.items().map_err(TauriError::from)
396        })?;
397
398        let mut vec = Vec::with_capacity(items.len());
399        for items in items {
400            vec.push(MenuItemKind::from_tauri(py, items)?);
401        }
402        Ok(vec)
403    }
404
405    fn set_as_app_menu(&self, py: Python<'_>) -> PyResult<()> {
406        py.allow_threads(|| {
407            let menu = self.0.inner_ref();
408            menu.set_as_app_menu().map_err(TauriError::from)?;
409            Ok(())
410        })
411    }
412
413    fn set_as_window_menu(
414        &self,
415        py: Python<'_>,
416        window: Py<ext_mod::window::Window>,
417    ) -> PyResult<()> {
418        py.allow_threads(|| {
419            let menu = self.0.inner_ref();
420            let window = window.get().0.inner_ref();
421            menu.set_as_window_menu(window.deref())
422                .map_err(TauriError::from)?;
423            Ok(())
424        })
425    }
426}
427
428/// See also: [tauri::menu::Submenu]
429#[pyclass(frozen)]
430#[non_exhaustive]
431pub struct Submenu(pub PyWrapper<PyWrapperT0<TauriSubmenu>>);
432
433impl Submenu {
434    fn new(menu: TauriSubmenu) -> Self {
435        Self(PyWrapper::new0(menu))
436    }
437
438    #[inline]
439    fn new_impl(
440        py: Python<'_>,
441        manager: &impl tauri::Manager<Runtime>,
442        text: &str,
443        enabled: bool,
444        id: Option<impl Into<menu::MenuId> + Send>,
445        items: Option<Vec<MenuItemKind>>,
446    ) -> PyResult<Self> {
447        unsafe {
448            py.allow_threads_unsend(manager, |manager| {
449                let menu = if let Some(id) = id {
450                    TauriSubmenu::with_id(manager, id, text, enabled)
451                } else {
452                    TauriSubmenu::new(manager, text, enabled)
453                }?;
454
455                if let Some(items) = items {
456                    MenuItemKind::append_items_to_menu(items.iter(), &menu)?;
457                }
458                tauri::Result::Ok(Self::new(menu))
459            })
460        }
461        .map_err(TauriError::from)
462        .map_err(PyErr::from)
463    }
464}
465
466// All methods must release the GIL, because `menu` call `run_on_main_thread` internally, which may block.
467#[pymethods]
468impl Submenu {
469    #[new]
470    fn __new__(py: Python<'_>, manager: ImplManager, text: &str, enabled: bool) -> PyResult<Self> {
471        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
472            py,
473            manager,
474            text,
475            enabled,
476            None::<&str>,
477            None
478        ))?
479    }
480
481    #[staticmethod]
482    fn with_id(
483        py: Python<'_>,
484        manager: ImplManager,
485        id: String,
486        text: &str,
487        enabled: bool,
488    ) -> PyResult<Self> {
489        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
490            py,
491            manager,
492            text,
493            enabled,
494            Some(MenuId(id)),
495            None
496        ))?
497    }
498
499    #[staticmethod]
500    fn with_items(
501        py: Python<'_>,
502        manager: ImplManager,
503        text: &str,
504        enabled: bool,
505        items: Vec<MenuItemKind>,
506    ) -> PyResult<Self> {
507        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
508            py,
509            manager,
510            text,
511            enabled,
512            None::<&str>,
513            Some(items)
514        ))?
515    }
516
517    #[staticmethod]
518    fn with_id_and_items(
519        py: Python<'_>,
520        manager: ImplManager,
521        id: String,
522        text: &str,
523        enabled: bool,
524        items: Vec<MenuItemKind>,
525    ) -> PyResult<Self> {
526        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
527            py,
528            manager,
529            text,
530            enabled,
531            Some(MenuId(id)),
532            Some(items),
533        ))?
534    }
535
536    fn app_handle(&self, py: Python<'_>) -> Py<ext_mod::AppHandle> {
537        let menu = self.0.inner_ref();
538        // TODO, PERF: release the GIL?
539        let app_handle = menu.app_handle().py_app_handle().clone_ref(py);
540        app_handle
541    }
542
543    fn id<'py>(&self, py: Python<'py>) -> Bound<'py, MenuID> {
544        let menu = self.0.inner_ref();
545        MenuID::intern(py, &menu.id().0)
546    }
547
548    fn append(&self, py: Python<'_>, item: MenuItemKind) -> PyResult<()> {
549        py.allow_threads(|| {
550            let menu = self.0.inner_ref();
551            item.append_to_menu(menu.deref())
552                .map_err(TauriError::from)?;
553            Ok(())
554        })
555    }
556
557    fn append_items(&self, py: Python<'_>, items: Vec<MenuItemKind>) -> PyResult<()> {
558        py.allow_threads(|| {
559            let menu = self.0.inner_ref();
560            MenuItemKind::append_items_to_menu(items.iter(), menu.deref())
561                .map_err(TauriError::from)?;
562            Ok(())
563        })
564    }
565
566    fn prepend(&self, py: Python<'_>, item: MenuItemKind) -> PyResult<()> {
567        py.allow_threads(|| {
568            let menu = self.0.inner_ref();
569            item.prepend_to_menu(menu.deref())
570                .map_err(TauriError::from)?;
571            Ok(())
572        })
573    }
574
575    fn prepend_items(&self, py: Python<'_>, items: Vec<MenuItemKind>) -> PyResult<()> {
576        py.allow_threads(|| {
577            let menu = self.0.inner_ref();
578            MenuItemKind::prepend_items_to_menu(items.iter(), menu.deref())
579                .map_err(TauriError::from)?;
580            Ok(())
581        })
582    }
583
584    fn insert(&self, py: Python<'_>, item: MenuItemKind, position: usize) -> PyResult<()> {
585        py.allow_threads(|| {
586            let menu = self.0.inner_ref();
587            item.insert_to_menu(menu.deref(), position)
588                .map_err(TauriError::from)?;
589            Ok(())
590        })
591    }
592
593    fn insert_items(
594        &self,
595        py: Python<'_>,
596        items: Vec<MenuItemKind>,
597        position: usize,
598    ) -> PyResult<()> {
599        py.allow_threads(|| {
600            let menu = self.0.inner_ref();
601            MenuItemKind::insert_items_to_menu(items.iter(), menu.deref(), position)
602                .map_err(TauriError::from)?;
603            Ok(())
604        })
605    }
606
607    fn remove(&self, py: Python<'_>, item: MenuItemKind) -> PyResult<()> {
608        py.allow_threads(|| {
609            let menu = self.0.inner_ref();
610            item.remove_from_menu(menu.deref())
611                .map_err(TauriError::from)?;
612            Ok(())
613        })
614    }
615
616    fn remove_at(&self, py: Python<'_>, position: usize) -> PyResult<Option<MenuItemKind>> {
617        let item_kind = py.allow_threads(|| {
618            let menu = self.0.inner_ref();
619            menu.remove_at(position)
620                .map_err(TauriError::from)
621                .map_err(PyErr::from)
622        })?;
623
624        let item_kind = match item_kind {
625            Some(item_kind) => Some(MenuItemKind::from_tauri(py, item_kind)?),
626            None => None,
627        };
628
629        Ok(item_kind)
630    }
631
632    fn get(&self, py: Python<'_>, id: &str) -> PyResult<Option<MenuItemKind>> {
633        let item_kind = py.allow_threads(|| {
634            let menu = self.0.inner_ref();
635            menu.get(id)
636        });
637
638        let item_kind = match item_kind {
639            Some(item_kind) => Some(MenuItemKind::from_tauri(py, item_kind)?),
640            None => None,
641        };
642
643        Ok(item_kind)
644    }
645
646    fn items(&self, py: Python<'_>) -> PyResult<Vec<MenuItemKind>> {
647        let items = py.allow_threads(|| {
648            let menu = self.0.inner_ref();
649            menu.items().map_err(TauriError::from)
650        })?;
651
652        let mut vec = Vec::with_capacity(items.len());
653        for items in items {
654            vec.push(MenuItemKind::from_tauri(py, items)?);
655        }
656        Ok(vec)
657    }
658
659    fn text(&self, py: Python<'_>) -> PyResult<String> {
660        py.allow_threads(|| {
661            let menu = self.0.inner_ref();
662            let text = menu.text().map_err(TauriError::from)?;
663            Ok(text)
664        })
665    }
666
667    fn set_text(&self, py: Python<'_>, text: &str) -> PyResult<()> {
668        py.allow_threads(|| {
669            let menu = self.0.inner_ref();
670            menu.set_text(text).map_err(TauriError::from)?;
671            Ok(())
672        })
673    }
674
675    fn is_enabled(&self, py: Python<'_>) -> PyResult<bool> {
676        py.allow_threads(|| {
677            let menu = self.0.inner_ref();
678            let enabled = menu.is_enabled().map_err(TauriError::from)?;
679            Ok(enabled)
680        })
681    }
682
683    fn set_enabled(&self, py: Python<'_>, enabled: bool) -> PyResult<()> {
684        py.allow_threads(|| {
685            let menu = self.0.inner_ref();
686            menu.set_enabled(enabled).map_err(TauriError::from)?;
687            Ok(())
688        })
689    }
690}
691
692/// See also: [tauri::menu::MenuItem]
693#[pyclass(frozen)]
694#[non_exhaustive]
695pub struct MenuItem(pub PyWrapper<PyWrapperT0<TauriMenuItem>>);
696
697impl MenuItem {
698    fn new(menu: TauriMenuItem) -> Self {
699        Self(PyWrapper::new0(menu))
700    }
701
702    #[inline]
703    fn new_impl(
704        py: Python<'_>,
705        manager: &impl tauri::Manager<Runtime>,
706        text: &str,
707        enabled: bool,
708        accelerator: Option<&str>,
709        id: Option<impl Into<menu::MenuId> + Send>,
710    ) -> PyResult<Self> {
711        unsafe {
712            py.allow_threads_unsend(manager, |manager| {
713                let menu = if let Some(id) = id {
714                    TauriMenuItem::with_id(manager, id, text, enabled, accelerator)
715                } else {
716                    TauriMenuItem::new(manager, text, enabled, accelerator)
717                }?;
718
719                tauri::Result::Ok(Self::new(menu))
720            })
721        }
722        .map_err(TauriError::from)
723        .map_err(PyErr::from)
724    }
725}
726
727#[pymethods]
728impl MenuItem {
729    #[new]
730    #[pyo3(signature = (manager, text, enabled, accelerator=None))]
731    fn __new__(
732        py: Python<'_>,
733        manager: ImplManager,
734        text: &str,
735        enabled: bool,
736        accelerator: Option<&str>,
737    ) -> PyResult<Self> {
738        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
739            py,
740            manager,
741            text,
742            enabled,
743            accelerator,
744            None::<&str>
745        ))?
746    }
747
748    #[staticmethod]
749    #[pyo3(signature = (manager, id, text, enabled, accelerator=None))]
750    fn with_id(
751        py: Python<'_>,
752        manager: ImplManager,
753        id: String,
754        text: &str,
755        enabled: bool,
756        accelerator: Option<&str>,
757    ) -> PyResult<Self> {
758        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
759            py,
760            manager,
761            text,
762            enabled,
763            accelerator,
764            Some(MenuId(id)),
765        ))?
766    }
767
768    fn app_handle(&self, py: Python<'_>) -> Py<ext_mod::AppHandle> {
769        let menu = self.0.inner_ref();
770        // TODO, PERF: release the GIL?
771        let app_handle = menu.app_handle().py_app_handle().clone_ref(py);
772        app_handle
773    }
774
775    fn id<'py>(&self, py: Python<'py>) -> Bound<'py, MenuID> {
776        let menu = self.0.inner_ref();
777        MenuID::intern(py, &menu.id().0)
778    }
779
780    fn text(&self, py: Python<'_>) -> PyResult<String> {
781        py.allow_threads(|| {
782            let menu = self.0.inner_ref();
783            let text = menu.text().map_err(TauriError::from)?;
784            Ok(text)
785        })
786    }
787
788    fn set_text(&self, py: Python<'_>, text: &str) -> PyResult<()> {
789        py.allow_threads(|| {
790            let menu = self.0.inner_ref();
791            menu.set_text(text).map_err(TauriError::from)?;
792            Ok(())
793        })
794    }
795
796    fn is_enabled(&self, py: Python<'_>) -> PyResult<bool> {
797        py.allow_threads(|| {
798            let menu = self.0.inner_ref();
799            let enabled = menu.is_enabled().map_err(TauriError::from)?;
800            Ok(enabled)
801        })
802    }
803
804    fn set_enabled(&self, py: Python<'_>, enabled: bool) -> PyResult<()> {
805        py.allow_threads(|| {
806            let menu = self.0.inner_ref();
807            menu.set_enabled(enabled).map_err(TauriError::from)?;
808            Ok(())
809        })
810    }
811
812    #[pyo3(signature = (accelerator))]
813    fn set_accelerator(&self, py: Python<'_>, accelerator: Option<&str>) -> PyResult<()> {
814        py.allow_threads(|| {
815            let menu = self.0.inner_ref();
816            menu.set_accelerator(accelerator)
817                .map_err(TauriError::from)?;
818            Ok(())
819        })
820    }
821}
822
823/// See also: [tauri::menu::PredefinedMenuItem]
824#[pyclass(frozen)]
825#[non_exhaustive]
826pub struct PredefinedMenuItem(pub PyWrapper<PyWrapperT0<TauriPredefinedMenuItem>>);
827
828impl PredefinedMenuItem {
829    fn new(menu: TauriPredefinedMenuItem) -> Self {
830        Self(PyWrapper::new0(menu))
831    }
832
833    #[inline]
834    fn delegate_inner<M, F>(py: Python<'_>, manager: &M, func: F) -> PyResult<Self>
835    where
836        M: tauri::Manager<Runtime>,
837        F: FnOnce(&M) -> tauri::Result<TauriPredefinedMenuItem> + Ungil + Send,
838    {
839        unsafe {
840            py.allow_threads_unsend(manager, |manager| {
841                let menu = func(manager)?;
842                tauri::Result::Ok(Self::new(menu))
843            })
844        }
845        .map_err(TauriError::from)
846        .map_err(PyErr::from)
847    }
848}
849
850#[pymethods]
851impl PredefinedMenuItem {
852    #[staticmethod]
853    fn separator(py: Python<'_>, manager: ImplManager) -> PyResult<Self> {
854        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
855            py,
856            manager,
857            move |manager| { TauriPredefinedMenuItem::separator(manager) }
858        ))?
859    }
860
861    #[staticmethod]
862    #[pyo3(signature = (manager, text=None))]
863    fn copy(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
864        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
865            py,
866            manager,
867            move |manager| { TauriPredefinedMenuItem::copy(manager, text) }
868        ))?
869    }
870
871    #[staticmethod]
872    #[pyo3(signature = (manager, text=None))]
873    fn cut(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
874        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
875            py,
876            manager,
877            move |manager| { TauriPredefinedMenuItem::cut(manager, text) }
878        ))?
879    }
880
881    #[staticmethod]
882    #[pyo3(signature = (manager, text=None))]
883    fn paste(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
884        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
885            py,
886            manager,
887            move |manager| { TauriPredefinedMenuItem::paste(manager, text) }
888        ))?
889    }
890
891    #[staticmethod]
892    #[pyo3(signature = (manager, text=None))]
893    fn select_all(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
894        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
895            py,
896            manager,
897            move |manager| { TauriPredefinedMenuItem::select_all(manager, text) }
898        ))?
899    }
900
901    #[staticmethod]
902    #[pyo3(signature = (manager, text=None))]
903    fn undo(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
904        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
905            py,
906            manager,
907            move |manager| { TauriPredefinedMenuItem::undo(manager, text) }
908        ))?
909    }
910
911    #[staticmethod]
912    #[pyo3(signature = (manager, text=None))]
913    fn redo(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
914        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
915            py,
916            manager,
917            move |manager| { TauriPredefinedMenuItem::redo(manager, text) }
918        ))?
919    }
920
921    #[staticmethod]
922    #[pyo3(signature = (manager, text=None))]
923    fn minimize(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
924        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
925            py,
926            manager,
927            move |manager| { TauriPredefinedMenuItem::minimize(manager, text) }
928        ))?
929    }
930
931    #[staticmethod]
932    #[pyo3(signature = (manager, text=None))]
933    fn maximize(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
934        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
935            py,
936            manager,
937            move |manager| { TauriPredefinedMenuItem::maximize(manager, text) }
938        ))?
939    }
940
941    #[staticmethod]
942    #[pyo3(signature = (manager, text=None))]
943    fn fullscreen(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
944        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
945            py,
946            manager,
947            move |manager| { TauriPredefinedMenuItem::fullscreen(manager, text) }
948        ))?
949    }
950
951    #[staticmethod]
952    #[pyo3(signature = (manager, text=None))]
953    fn hide(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
954        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
955            py,
956            manager,
957            move |manager| { TauriPredefinedMenuItem::hide(manager, text) }
958        ))?
959    }
960
961    #[staticmethod]
962    #[pyo3(signature = (manager, text=None))]
963    fn hide_others(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
964        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
965            py,
966            manager,
967            move |manager| { TauriPredefinedMenuItem::hide_others(manager, text) }
968        ))?
969    }
970
971    #[staticmethod]
972    #[pyo3(signature = (manager, text=None))]
973    fn show_all(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
974        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
975            py,
976            manager,
977            move |manager| { TauriPredefinedMenuItem::show_all(manager, text) }
978        ))?
979    }
980
981    #[staticmethod]
982    #[pyo3(signature = (manager, text=None))]
983    fn close_window(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
984        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
985            py,
986            manager,
987            move |manager| { TauriPredefinedMenuItem::close_window(manager, text) }
988        ))?
989    }
990
991    #[staticmethod]
992    #[pyo3(signature = (manager, text=None))]
993    fn quit(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
994        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
995            py,
996            manager,
997            move |manager| { TauriPredefinedMenuItem::quit(manager, text) }
998        ))?
999    }
1000
1001    #[staticmethod]
1002    #[pyo3(signature = (manager, text=None, metadata=None))]
1003    fn about(
1004        py: Python<'_>,
1005        manager: ImplManager,
1006        text: Option<&str>,
1007        metadata: Option<Py<AboutMetadata>>,
1008    ) -> PyResult<Self> {
1009        let metadata = match metadata {
1010            Some(ref metadata) => Some(metadata.get().to_tauri(py)?),
1011            None => None,
1012        };
1013
1014        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
1015            py,
1016            manager,
1017            move |manager| { TauriPredefinedMenuItem::about(manager, text, metadata) }
1018        ))?
1019    }
1020
1021    #[staticmethod]
1022    #[pyo3(signature = (manager, text=None))]
1023    fn services(py: Python<'_>, manager: ImplManager, text: Option<&str>) -> PyResult<Self> {
1024        manager_method_impl!(py, &manager, |py, manager| Self::delegate_inner(
1025            py,
1026            manager,
1027            move |manager| { TauriPredefinedMenuItem::services(manager, text) }
1028        ))?
1029    }
1030
1031    fn app_handle(&self, py: Python<'_>) -> Py<ext_mod::AppHandle> {
1032        let menu = self.0.inner_ref();
1033        // TODO, PERF: release the GIL?
1034        let app_handle = menu.app_handle().py_app_handle().clone_ref(py);
1035        app_handle
1036    }
1037
1038    fn id<'py>(&self, py: Python<'py>) -> Bound<'py, MenuID> {
1039        let menu = self.0.inner_ref();
1040        MenuID::intern(py, &menu.id().0)
1041    }
1042
1043    fn text(&self, py: Python<'_>) -> PyResult<String> {
1044        py.allow_threads(|| {
1045            let menu = self.0.inner_ref();
1046            let text = menu.text().map_err(TauriError::from)?;
1047            Ok(text)
1048        })
1049    }
1050
1051    fn set_text(&self, py: Python<'_>, text: &str) -> PyResult<()> {
1052        py.allow_threads(|| {
1053            let menu = self.0.inner_ref();
1054            menu.set_text(text).map_err(TauriError::from)?;
1055            Ok(())
1056        })
1057    }
1058}
1059
1060/// See also: [tauri::menu::CheckMenuItem]
1061#[pyclass(frozen)]
1062#[non_exhaustive]
1063pub struct CheckMenuItem(pub PyWrapper<PyWrapperT0<TauriCheckMenuItem>>);
1064
1065impl CheckMenuItem {
1066    fn new(menu: TauriCheckMenuItem) -> Self {
1067        Self(PyWrapper::new0(menu))
1068    }
1069
1070    #[inline]
1071    fn new_impl(
1072        py: Python<'_>,
1073        manager: &impl tauri::Manager<Runtime>,
1074        text: &str,
1075        enabled: bool,
1076        checked: bool,
1077        accelerator: Option<&str>,
1078        id: Option<impl Into<menu::MenuId> + Send>,
1079    ) -> PyResult<Self> {
1080        unsafe {
1081            py.allow_threads_unsend(manager, |manager| {
1082                let menu = if let Some(id) = id {
1083                    TauriCheckMenuItem::with_id(manager, id, text, enabled, checked, accelerator)
1084                } else {
1085                    TauriCheckMenuItem::new(manager, text, enabled, checked, accelerator)
1086                }?;
1087
1088                tauri::Result::Ok(Self::new(menu))
1089            })
1090        }
1091        .map_err(TauriError::from)
1092        .map_err(PyErr::from)
1093    }
1094}
1095
1096#[pymethods]
1097impl CheckMenuItem {
1098    #[new]
1099    #[pyo3(signature = (manager, text, enabled, checked, accelerator=None))]
1100    fn __new__(
1101        py: Python<'_>,
1102        manager: ImplManager,
1103        text: &str,
1104        enabled: bool,
1105        checked: bool,
1106        accelerator: Option<&str>,
1107    ) -> PyResult<Self> {
1108        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
1109            py,
1110            manager,
1111            text,
1112            enabled,
1113            checked,
1114            accelerator,
1115            None::<&str>,
1116        ))?
1117    }
1118
1119    #[staticmethod]
1120    #[pyo3(signature = (manager, id, text, enabled, checked, accelerator=None))]
1121    fn with_id(
1122        py: Python<'_>,
1123        manager: ImplManager,
1124        id: String,
1125        text: &str,
1126        enabled: bool,
1127        checked: bool,
1128        accelerator: Option<&str>,
1129    ) -> PyResult<Self> {
1130        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
1131            py,
1132            manager,
1133            text,
1134            enabled,
1135            checked,
1136            accelerator,
1137            Some(MenuId(id)),
1138        ))?
1139    }
1140
1141    fn app_handle(&self, py: Python<'_>) -> Py<ext_mod::AppHandle> {
1142        let menu = self.0.inner_ref();
1143        // TODO, PERF: release the GIL?
1144        let app_handle = menu.app_handle().py_app_handle().clone_ref(py);
1145        app_handle
1146    }
1147
1148    fn id<'py>(&self, py: Python<'py>) -> Bound<'py, MenuID> {
1149        let menu = self.0.inner_ref();
1150        MenuID::intern(py, &menu.id().0)
1151    }
1152
1153    fn text(&self, py: Python<'_>) -> PyResult<String> {
1154        py.allow_threads(|| {
1155            let menu = self.0.inner_ref();
1156            let text = menu.text().map_err(TauriError::from)?;
1157            Ok(text)
1158        })
1159    }
1160
1161    fn set_text(&self, py: Python<'_>, text: &str) -> PyResult<()> {
1162        py.allow_threads(|| {
1163            let menu = self.0.inner_ref();
1164            menu.set_text(text).map_err(TauriError::from)?;
1165            Ok(())
1166        })
1167    }
1168
1169    fn is_enabled(&self, py: Python<'_>) -> PyResult<bool> {
1170        py.allow_threads(|| {
1171            let menu = self.0.inner_ref();
1172            let enabled = menu.is_enabled().map_err(TauriError::from)?;
1173            Ok(enabled)
1174        })
1175    }
1176
1177    fn set_enabled(&self, py: Python<'_>, enabled: bool) -> PyResult<()> {
1178        py.allow_threads(|| {
1179            let menu = self.0.inner_ref();
1180            menu.set_enabled(enabled).map_err(TauriError::from)?;
1181            Ok(())
1182        })
1183    }
1184
1185    #[pyo3(signature = (accelerator))]
1186    fn set_accelerator(&self, py: Python<'_>, accelerator: Option<&str>) -> PyResult<()> {
1187        py.allow_threads(|| {
1188            let menu = self.0.inner_ref();
1189            menu.set_accelerator(accelerator)
1190                .map_err(TauriError::from)?;
1191            Ok(())
1192        })
1193    }
1194
1195    fn is_checked(&self, py: Python<'_>) -> PyResult<bool> {
1196        py.allow_threads(|| {
1197            let menu = self.0.inner_ref();
1198            let checked = menu.is_checked().map_err(TauriError::from)?;
1199            Ok(checked)
1200        })
1201    }
1202
1203    fn set_checked(&self, py: Python<'_>, checked: bool) -> PyResult<()> {
1204        py.allow_threads(|| {
1205            let menu = self.0.inner_ref();
1206            menu.set_checked(checked).map_err(TauriError::from)?;
1207            Ok(())
1208        })
1209    }
1210}
1211
1212trait PyStrToRs {
1213    type Output;
1214    fn to_rs(&self, py: Python<'_>) -> Self::Output;
1215}
1216
1217impl PyStrToRs for Py<PyString> {
1218    type Output = PyResult<String>;
1219    fn to_rs(&self, py: Python<'_>) -> Self::Output {
1220        // TODO, PERF: once we drop py39 support, we can use [PyStringMethods::to_str] directly.
1221        Ok(self.to_cow(py)?.into_owned())
1222    }
1223}
1224
1225impl PyStrToRs for Option<Py<PyString>> {
1226    type Output = PyResult<Option<String>>;
1227    fn to_rs(&self, py: Python<'_>) -> Self::Output {
1228        match self {
1229            Some(py_str) => Ok(Some(py_str.to_rs(py)?)),
1230            None => Ok(None),
1231        }
1232    }
1233}
1234
1235impl PyStrToRs for Option<Vec<Py<PyString>>> {
1236    type Output = PyResult<Option<Vec<String>>>;
1237    fn to_rs(&self, py: Python<'_>) -> Self::Output {
1238        match self {
1239            Some(py_str_vec) => {
1240                let mut vec = Vec::with_capacity(py_str_vec.len());
1241                for py_str in py_str_vec {
1242                    vec.push(py_str.to_rs(py)?);
1243                }
1244                Ok(Some(vec))
1245            }
1246            None => Ok(None),
1247        }
1248    }
1249}
1250
1251/// See also [tauri::menu::AboutMetadata].
1252#[pyclass(frozen)]
1253#[non_exhaustive]
1254pub struct AboutMetadata {
1255    name: Option<Py<PyString>>,
1256    version: Option<Py<PyString>>,
1257    short_version: Option<Py<PyString>>,
1258    authors: Option<Vec<Py<PyString>>>,
1259    comments: Option<Py<PyString>>,
1260    copyright: Option<Py<PyString>>,
1261    license: Option<Py<PyString>>,
1262    website: Option<Py<PyString>>,
1263    website_label: Option<Py<PyString>>,
1264    credits: Option<Py<PyString>>,
1265    icon: Option<Py<ext_mod::image::Image>>,
1266}
1267
1268impl AboutMetadata {
1269    fn to_tauri<'a>(&'a self, py: Python<'_>) -> PyResult<menu::AboutMetadata<'a>> {
1270        let about_metadata = menu::AboutMetadata {
1271            name: self.name.to_rs(py)?,
1272            version: self.version.to_rs(py)?,
1273            short_version: self.short_version.to_rs(py)?,
1274            authors: self.authors.to_rs(py)?,
1275            comments: self.comments.to_rs(py)?,
1276            copyright: self.copyright.to_rs(py)?,
1277            license: self.license.to_rs(py)?,
1278            website: self.website.to_rs(py)?,
1279            website_label: self.website_label.to_rs(py)?,
1280            credits: self.credits.to_rs(py)?,
1281            icon: self.icon.as_ref().map(|icon| icon.get().to_tauri(py)),
1282        };
1283        Ok(about_metadata)
1284    }
1285}
1286
1287#[pymethods]
1288impl AboutMetadata {
1289    #[new]
1290    #[pyo3(signature = (
1291        *,
1292        name=None,
1293        version=None,
1294        short_version=None,
1295        authors=None,
1296        comments=None,
1297        copyright=None,
1298        license=None,
1299        website=None,
1300        website_label=None,
1301        credits=None,
1302        icon=None
1303    ))]
1304    #[expect(clippy::too_many_arguments)]
1305    const fn __new__(
1306        name: Option<Py<PyString>>,
1307        version: Option<Py<PyString>>,
1308        short_version: Option<Py<PyString>>,
1309        authors: Option<Vec<Py<PyString>>>,
1310        comments: Option<Py<PyString>>,
1311        copyright: Option<Py<PyString>>,
1312        license: Option<Py<PyString>>,
1313        website: Option<Py<PyString>>,
1314        website_label: Option<Py<PyString>>,
1315        credits: Option<Py<PyString>>,
1316        icon: Option<Py<ext_mod::image::Image>>,
1317    ) -> Self {
1318        Self {
1319            name,
1320            version,
1321            short_version,
1322            authors,
1323            comments,
1324            copyright,
1325            license,
1326            website,
1327            website_label,
1328            credits,
1329            icon,
1330        }
1331    }
1332}
1333
1334enum IconOrNative<'a> {
1335    Icon(Option<tauri::image::Image<'a>>),
1336    Native(Option<menu::NativeIcon>),
1337}
1338
1339/// See also: [tauri::menu::IconMenuItem]
1340#[pyclass(frozen)]
1341#[non_exhaustive]
1342pub struct IconMenuItem(pub PyWrapper<PyWrapperT0<TauriIconMenuItem>>);
1343
1344impl IconMenuItem {
1345    fn new(menu: TauriIconMenuItem) -> Self {
1346        Self(PyWrapper::new0(menu))
1347    }
1348
1349    #[inline]
1350    fn new_impl(
1351        py: Python<'_>,
1352        manager: &impl tauri::Manager<Runtime>,
1353        text: &str,
1354        enabled: bool,
1355        icon_or_native: IconOrNative<'_>,
1356        accelerator: Option<&str>,
1357        id: Option<impl Into<menu::MenuId> + Send>,
1358    ) -> PyResult<Self> {
1359        unsafe {
1360            py.allow_threads_unsend(manager, |manager| {
1361                let menu = if let Some(id) = id {
1362                    match icon_or_native {
1363                        IconOrNative::Icon(icon) => TauriIconMenuItem::with_id(
1364                            manager,
1365                            id,
1366                            text,
1367                            enabled,
1368                            icon,
1369                            accelerator,
1370                        ),
1371                        IconOrNative::Native(native_icon) => {
1372                            TauriIconMenuItem::with_id_and_native_icon(
1373                                manager,
1374                                id,
1375                                text,
1376                                enabled,
1377                                native_icon,
1378                                accelerator,
1379                            )
1380                        }
1381                    }
1382                } else {
1383                    match icon_or_native {
1384                        IconOrNative::Icon(icon) => {
1385                            TauriIconMenuItem::new(manager, text, enabled, icon, accelerator)
1386                        }
1387                        IconOrNative::Native(native_icon) => TauriIconMenuItem::with_native_icon(
1388                            manager,
1389                            text,
1390                            enabled,
1391                            native_icon,
1392                            accelerator,
1393                        ),
1394                    }
1395                }?;
1396
1397                tauri::Result::Ok(Self::new(menu))
1398            })
1399        }
1400        .map_err(TauriError::from)
1401        .map_err(PyErr::from)
1402    }
1403}
1404
1405#[pymethods]
1406impl IconMenuItem {
1407    #[new]
1408    #[pyo3(signature = (manager, text, enabled, icon=None, accelerator=None))]
1409    fn __new__(
1410        py: Python<'_>,
1411        manager: ImplManager,
1412        text: &str,
1413        enabled: bool,
1414        icon: Option<Py<ext_mod::image::Image>>,
1415        accelerator: Option<&str>,
1416    ) -> PyResult<Self> {
1417        let icon = icon.as_ref().map(|icon| icon.get().to_tauri(py));
1418        let icon = IconOrNative::Icon(icon);
1419
1420        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
1421            py,
1422            manager,
1423            text,
1424            enabled,
1425            icon,
1426            accelerator,
1427            None::<&str>,
1428        ))?
1429    }
1430
1431    #[staticmethod]
1432    #[pyo3(signature = (manager, id, text, enabled, icon=None, accelerator=None))]
1433    fn with_id(
1434        py: Python<'_>,
1435        manager: ImplManager,
1436        id: String,
1437        text: &str,
1438        enabled: bool,
1439        icon: Option<Py<ext_mod::image::Image>>,
1440        accelerator: Option<&str>,
1441    ) -> PyResult<Self> {
1442        let icon = icon.as_ref().map(|icon| icon.get().to_tauri(py));
1443        let icon = IconOrNative::Icon(icon);
1444
1445        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
1446            py,
1447            manager,
1448            text,
1449            enabled,
1450            icon,
1451            accelerator,
1452            Some(MenuId(id)),
1453        ))?
1454    }
1455
1456    #[staticmethod]
1457    #[pyo3(signature = (manager, text, enabled, native_icon=None, accelerator=None))]
1458    fn with_native_icon(
1459        py: Python<'_>,
1460        manager: ImplManager,
1461        text: &str,
1462        enabled: bool,
1463        native_icon: Option<NativeIcon>,
1464        accelerator: Option<&str>,
1465    ) -> PyResult<Self> {
1466        let native_icon = native_icon.map(|native_icon| native_icon.into());
1467        let native_icon = IconOrNative::Native(native_icon);
1468
1469        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
1470            py,
1471            manager,
1472            text,
1473            enabled,
1474            native_icon,
1475            accelerator,
1476            None::<&str>,
1477        ))?
1478    }
1479
1480    #[staticmethod]
1481    #[pyo3(signature = (manager, id, text, enabled, native_icon=None, accelerator=None))]
1482    fn with_id_and_native_icon(
1483        py: Python<'_>,
1484        manager: ImplManager,
1485        id: String,
1486        text: &str,
1487        enabled: bool,
1488        native_icon: Option<NativeIcon>,
1489        accelerator: Option<&str>,
1490    ) -> PyResult<Self> {
1491        let native_icon = native_icon.map(|native_icon| native_icon.into());
1492        let native_icon = IconOrNative::Native(native_icon);
1493
1494        manager_method_impl!(py, &manager, |py, manager| Self::new_impl(
1495            py,
1496            manager,
1497            text,
1498            enabled,
1499            native_icon,
1500            accelerator,
1501            Some(MenuId(id)),
1502        ))?
1503    }
1504
1505    fn app_handle(&self, py: Python<'_>) -> Py<ext_mod::AppHandle> {
1506        let menu = self.0.inner_ref();
1507        // TODO, PERF: release the GIL?
1508        let app_handle = menu.app_handle().py_app_handle().clone_ref(py);
1509        app_handle
1510    }
1511
1512    fn id<'py>(&self, py: Python<'py>) -> Bound<'py, MenuID> {
1513        let menu = self.0.inner_ref();
1514        MenuID::intern(py, &menu.id().0)
1515    }
1516
1517    fn text(&self, py: Python<'_>) -> PyResult<String> {
1518        py.allow_threads(|| {
1519            let menu = self.0.inner_ref();
1520            let text = menu.text().map_err(TauriError::from)?;
1521            Ok(text)
1522        })
1523    }
1524
1525    fn set_text(&self, py: Python<'_>, text: &str) -> PyResult<()> {
1526        py.allow_threads(|| {
1527            let menu = self.0.inner_ref();
1528            menu.set_text(text).map_err(TauriError::from)?;
1529            Ok(())
1530        })
1531    }
1532
1533    fn is_enabled(&self, py: Python<'_>) -> PyResult<bool> {
1534        py.allow_threads(|| {
1535            let menu = self.0.inner_ref();
1536            let enabled = menu.is_enabled().map_err(TauriError::from)?;
1537            Ok(enabled)
1538        })
1539    }
1540
1541    fn set_enabled(&self, py: Python<'_>, enabled: bool) -> PyResult<()> {
1542        py.allow_threads(|| {
1543            let menu = self.0.inner_ref();
1544            menu.set_enabled(enabled).map_err(TauriError::from)?;
1545            Ok(())
1546        })
1547    }
1548
1549    #[pyo3(signature = (accelerator))]
1550    fn set_accelerator(&self, py: Python<'_>, accelerator: Option<&str>) -> PyResult<()> {
1551        py.allow_threads(|| {
1552            let menu = self.0.inner_ref();
1553            menu.set_accelerator(accelerator)
1554                .map_err(TauriError::from)?;
1555            Ok(())
1556        })
1557    }
1558
1559    #[pyo3(signature = (icon))]
1560    fn set_icon(&self, py: Python<'_>, icon: Option<Py<ext_mod::image::Image>>) -> PyResult<()> {
1561        let icon = icon.as_ref().map(|icon| icon.get().to_tauri(py));
1562        py.allow_threads(|| {
1563            let menu = self.0.inner_ref();
1564            menu.set_icon(icon).map_err(TauriError::from)?;
1565            Ok(())
1566        })
1567    }
1568
1569    #[pyo3(signature = (native_icon))]
1570    fn set_native_icon(&self, py: Python<'_>, native_icon: Option<NativeIcon>) -> PyResult<()> {
1571        let native_icon = native_icon.map(|native_icon| native_icon.into());
1572        py.allow_threads(|| {
1573            let menu = self.0.inner_ref();
1574            menu.set_native_icon(native_icon)
1575                .map_err(TauriError::from)?;
1576            Ok(())
1577        })
1578    }
1579}
1580
1581macro_rules! native_icon_impl {
1582    ($ident:ident => : $($variant:ident),*) => {
1583        /// See also: [tauri::menu::NativeIcon]
1584        #[pyclass(frozen, eq, eq_int)]
1585        #[derive(PartialEq, Clone, Copy)]
1586        pub enum $ident {
1587            $($variant,)*
1588        }
1589
1590        impl From<$ident> for tauri::menu::NativeIcon {
1591            fn from(icon: $ident) -> Self {
1592                match icon {
1593                    $($ident::$variant => tauri::menu::NativeIcon::$variant,)*
1594                }
1595            }
1596        }
1597
1598        impl From<tauri::menu::NativeIcon> for $ident {
1599            fn from(icon: tauri::menu::NativeIcon) -> Self {
1600                match icon {
1601                    $(tauri::menu::NativeIcon::$variant => $ident::$variant,)*
1602                }
1603            }
1604        }
1605
1606    };
1607}
1608
1609native_icon_impl!(
1610    NativeIcon => :
1611    Add,
1612    Advanced,
1613    Bluetooth,
1614    Bookmarks,
1615    Caution,
1616    ColorPanel,
1617    ColumnView,
1618    Computer,
1619    EnterFullScreen,
1620    Everyone,
1621    ExitFullScreen,
1622    FlowView,
1623    Folder,
1624    FolderBurnable,
1625    FolderSmart,
1626    FollowLinkFreestanding,
1627    FontPanel,
1628    GoLeft,
1629    GoRight,
1630    Home,
1631    IChatTheater,
1632    IconView,
1633    Info,
1634    InvalidDataFreestanding,
1635    LeftFacingTriangle,
1636    ListView,
1637    LockLocked,
1638    LockUnlocked,
1639    MenuMixedState,
1640    MenuOnState,
1641    MobileMe,
1642    MultipleDocuments,
1643    Network,
1644    Path,
1645    PreferencesGeneral,
1646    QuickLook,
1647    RefreshFreestanding,
1648    Refresh,
1649    Remove,
1650    RevealFreestanding,
1651    RightFacingTriangle,
1652    Share,
1653    Slideshow,
1654    SmartBadge,
1655    StatusAvailable,
1656    StatusNone,
1657    StatusPartiallyAvailable,
1658    StatusUnavailable,
1659    StopProgressFreestanding,
1660    StopProgress,
1661    TrashEmpty,
1662    TrashFull,
1663    User,
1664    UserAccounts,
1665    UserGroup,
1666    UserGuest
1667);
1668
1669/// The Implementers of [tauri::menu::ContextMenu].
1670#[derive(FromPyObject, IntoPyObject, IntoPyObjectRef)]
1671#[non_exhaustive]
1672pub enum ImplContextMenu {
1673    Menu(Py<Menu>),
1674    Submenu(Py<Submenu>),
1675}
1676
1677impl ImplContextMenu {
1678    pub(crate) fn _delegate_inner_ref<M, R>(menu: &M, f: impl FnOnce(&M) -> R) -> R
1679    where
1680        M: menu::ContextMenu,
1681    {
1682        f(menu)
1683    }
1684}
1685
1686/// see [crate::ext_mod::manager_method_impl]
1687macro_rules! context_menu_impl {
1688    // impl
1689    ($menu:expr, $f0:expr, $f1:expr) => {{
1690        use $crate::ext_mod::menu::ImplContextMenu;
1691
1692        let menu: &ImplContextMenu = $menu;
1693        match menu {
1694            ImplContextMenu::Menu(v) => {
1695                ImplContextMenu::_delegate_inner_ref(&*v.get().0.inner_ref(), $f0)
1696            }
1697            ImplContextMenu::Submenu(v) => {
1698                ImplContextMenu::_delegate_inner_ref(&*v.get().0.inner_ref(), $f1)
1699            }
1700        }
1701    }};
1702
1703    // entry0
1704    ($menu:expr, $($f:tt)*) => {
1705        context_menu_impl!($menu, $($f)*, $($f)*)
1706    };
1707}
1708
1709pub(crate) use context_menu_impl;
1710
1711/// See also: [tauri::menu::ContextMenu].
1712#[pyclass(frozen)]
1713#[non_exhaustive]
1714pub struct ContextMenu;
1715
1716#[pymethods]
1717impl ContextMenu {
1718    #[staticmethod]
1719    fn popup(
1720        py: Python<'_>,
1721        slf: ImplContextMenu,
1722        window: Py<ext_mod::window::Window>,
1723    ) -> PyResult<()> {
1724        py.allow_threads(|| {
1725            let window = window.get().0.inner_ref().to_owned();
1726            context_menu_impl!(&slf, |menu| {
1727                menu.popup(window)
1728                    .map_err(TauriError::from)
1729                    .map_err(PyErr::from)
1730            })
1731        })
1732    }
1733
1734    #[staticmethod]
1735    fn popup_at(
1736        py: Python<'_>,
1737        slf: ImplContextMenu,
1738        window: Py<ext_mod::window::Window>,
1739        position: Py<ext_mod::Position>,
1740    ) -> PyResult<()> {
1741        let position = position.get().to_tauri(py)?;
1742        py.allow_threads(|| {
1743            let window = window.get().0.inner_ref().to_owned();
1744            context_menu_impl!(&slf, |menu| {
1745                menu.popup_at(window, position)
1746                    .map_err(TauriError::from)
1747                    .map_err(PyErr::from)
1748            })
1749        })
1750    }
1751}