1use std::sync::Arc;
6
7use super::run_item_main_thread;
8use super::Submenu;
9use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind};
10use crate::menu::NativeIcon;
11use crate::menu::SubmenuInner;
12use crate::run_main_thread;
13use crate::{AppHandle, Manager, Position, Runtime, Window};
14use muda::{ContextMenu, Icon as MudaIcon, MenuId};
15
16impl<R: Runtime> super::ContextMenu for Submenu<R> {
17 #[cfg(target_os = "windows")]
18 fn hpopupmenu(&self) -> crate::Result<isize> {
19 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().hpopupmenu())
20 }
21
22 fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {
23 self.popup_inner(window, None::<Position>)
24 }
25
26 fn popup_at<T: Runtime, P: Into<Position>>(
27 &self,
28 window: Window<T>,
29 position: P,
30 ) -> crate::Result<()> {
31 self.popup_inner(window, Some(position))
32 }
33}
34
35impl<R: Runtime> ContextMenuBase for Submenu<R> {
36 fn popup_inner<T: Runtime, P: Into<crate::Position>>(
37 &self,
38 window: crate::Window<T>,
39 position: Option<P>,
40 ) -> crate::Result<()> {
41 let position = position.map(Into::into);
42 run_item_main_thread!(self, move |self_: Self| {
43 #[cfg(target_os = "macos")]
44 if let Ok(view) = window.ns_view() {
45 unsafe {
46 self_
47 .inner()
48 .show_context_menu_for_nsview(view as _, position);
49 }
50 }
51
52 #[cfg(any(
53 target_os = "linux",
54 target_os = "dragonfly",
55 target_os = "freebsd",
56 target_os = "netbsd",
57 target_os = "openbsd"
58 ))]
59 if let Ok(w) = window.gtk_window() {
60 self_
61 .inner()
62 .show_context_menu_for_gtk_window(w.as_ref(), position);
63 }
64
65 #[cfg(windows)]
66 if let Ok(hwnd) = window.hwnd() {
67 unsafe {
68 self_
69 .inner()
70 .show_context_menu_for_hwnd(hwnd.0 as _, position);
71 }
72 }
73 })
74 }
75
76 fn inner_context(&self) -> &dyn muda::ContextMenu {
77 (*self.0).as_ref()
78 }
79
80 fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {
81 Box::new((*self.0).as_ref().clone())
82 }
83}
84
85impl<R: Runtime> Submenu<R> {
86 pub fn new<M: Manager<R>, S: AsRef<str>>(
88 manager: &M,
89 text: S,
90 enabled: bool,
91 ) -> crate::Result<Self> {
92 let handle = manager.app_handle();
93 let app_handle = handle.clone();
94
95 let text = text.as_ref().to_owned();
96
97 let submenu = run_main_thread!(handle, || {
98 let submenu = muda::Submenu::new(text, enabled);
99 SubmenuInner {
100 id: submenu.id().clone(),
101 inner: Some(submenu),
102 app_handle,
103 }
104 })?;
105
106 Ok(Self(Arc::new(submenu)))
107 }
108
109 pub fn new_with_icon<M: Manager<R>, S: AsRef<str>>(
111 manager: &M,
112 text: S,
113 enabled: bool,
114 icon: Option<crate::image::Image<'_>>,
115 ) -> crate::Result<Self> {
116 let handle = manager.app_handle();
117 let app_handle = handle.clone();
118 let text = text.as_ref().to_owned();
119 let icon_data = icon.map(|i| (i.rgba().to_vec(), i.width(), i.height()));
120 let submenu = run_main_thread!(handle, || {
121 let submenu = muda::Submenu::new(text, enabled);
122 if let Some((rgba, width, height)) = icon_data.clone() {
123 submenu.set_icon(Some(MudaIcon::from_rgba(rgba, width, height).unwrap()));
124 }
125 SubmenuInner {
126 id: submenu.id().clone(),
127 inner: Some(submenu),
128 app_handle,
129 }
130 })?;
131 Ok(Self(Arc::new(submenu)))
132 }
133
134 pub fn new_with_native_icon<M: Manager<R>, S: AsRef<str>>(
136 manager: &M,
137 text: S,
138 enabled: bool,
139 icon: Option<NativeIcon>,
140 ) -> crate::Result<Self> {
141 let handle = manager.app_handle();
142 let app_handle = handle.clone();
143 let text = text.as_ref().to_owned();
144 let submenu = run_main_thread!(handle, || {
145 let submenu = muda::Submenu::new(text, enabled);
146 if let Some(icon) = icon {
147 submenu.set_native_icon(Some(icon.into()));
148 }
149 SubmenuInner {
150 id: submenu.id().clone(),
151 inner: Some(submenu),
152 app_handle,
153 }
154 })?;
155 Ok(Self(Arc::new(submenu)))
156 }
157
158 pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
160 manager: &M,
161 id: I,
162 text: S,
163 enabled: bool,
164 ) -> crate::Result<Self> {
165 let handle = manager.app_handle();
166 let app_handle = handle.clone();
167
168 let id = id.into();
169 let text = text.as_ref().to_owned();
170
171 let submenu = run_main_thread!(handle, || {
172 let submenu = muda::Submenu::with_id(id.clone(), text, enabled);
173 SubmenuInner {
174 id,
175 inner: Some(submenu),
176 app_handle,
177 }
178 })?;
179
180 Ok(Self(Arc::new(submenu)))
181 }
182
183 pub fn with_id_and_icon<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
185 manager: &M,
186 id: I,
187 text: S,
188 enabled: bool,
189 icon: Option<crate::image::Image<'_>>,
190 ) -> crate::Result<Self> {
191 let handle = manager.app_handle();
192 let app_handle = handle.clone();
193 let id = id.into();
194 let text = text.as_ref().to_owned();
195 let icon_data = icon.map(|i| (i.rgba().to_vec(), i.width(), i.height()));
196 let submenu = run_main_thread!(handle, || {
197 let submenu = muda::Submenu::with_id(id.clone(), text, enabled);
198 if let Some((rgba, width, height)) = icon_data.clone() {
199 submenu.set_icon(Some(MudaIcon::from_rgba(rgba, width, height).unwrap()));
200 }
201 SubmenuInner {
202 id,
203 inner: Some(submenu),
204 app_handle,
205 }
206 })?;
207 Ok(Self(Arc::new(submenu)))
208 }
209
210 pub fn with_id_and_native_icon<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
212 manager: &M,
213 id: I,
214 text: S,
215 enabled: bool,
216 icon: Option<NativeIcon>,
217 ) -> crate::Result<Self> {
218 let handle = manager.app_handle();
219 let app_handle = handle.clone();
220 let id = id.into();
221 let text = text.as_ref().to_owned();
222 let submenu = run_main_thread!(handle, || {
223 let submenu = muda::Submenu::with_id(id.clone(), text, enabled);
224 if let Some(icon) = icon {
225 submenu.set_native_icon(Some(icon.into()));
226 }
227 SubmenuInner {
228 id,
229 inner: Some(submenu),
230 app_handle,
231 }
232 })?;
233 Ok(Self(Arc::new(submenu)))
234 }
235
236 pub fn with_items<M: Manager<R>, S: AsRef<str>>(
238 manager: &M,
239 text: S,
240 enabled: bool,
241 items: &[&dyn IsMenuItem<R>],
242 ) -> crate::Result<Self> {
243 let menu = Self::new(manager, text, enabled)?;
244 menu.append_items(items)?;
245 Ok(menu)
246 }
247
248 pub fn with_id_and_items<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
251 manager: &M,
252 id: I,
253 text: S,
254 enabled: bool,
255 items: &[&dyn IsMenuItem<R>],
256 ) -> crate::Result<Self> {
257 let menu = Self::with_id(manager, id, text, enabled)?;
258 menu.append_items(items)?;
259 Ok(menu)
260 }
261
262 pub(crate) fn inner(&self) -> &muda::Submenu {
263 (*self.0).as_ref()
264 }
265
266 pub fn app_handle(&self) -> &AppHandle<R> {
268 &self.0.app_handle
269 }
270
271 pub fn id(&self) -> &MenuId {
273 &self.0.id
274 }
275
276 pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
278 let kind = item.kind();
279 run_item_main_thread!(self, |self_: Self| {
280 (*self_.0).as_ref().append(kind.inner().inner_muda())
281 })?
282 .map_err(Into::into)
283 }
284
285 pub fn append_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
287 for item in items {
288 self.append(*item)?
289 }
290
291 Ok(())
292 }
293
294 pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
296 let kind = item.kind();
297 run_item_main_thread!(self, |self_: Self| {
298 (*self_.0).as_ref().prepend(kind.inner().inner_muda())
299 })?
300 .map_err(Into::into)
301 }
302
303 pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {
305 self.insert_items(items, 0)
306 }
307
308 pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
310 let kind = item.kind();
311 run_item_main_thread!(self, |self_: Self| {
312 (*self_.0)
313 .as_ref()
314 .insert(kind.inner().inner_muda(), position)
315 })?
316 .map_err(Into::into)
317 }
318
319 pub fn insert_items(&self, items: &[&dyn IsMenuItem<R>], position: usize) -> crate::Result<()> {
321 for (i, item) in items.iter().enumerate() {
322 self.insert(*item, position + i)?
323 }
324
325 Ok(())
326 }
327
328 pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
330 let kind = item.kind();
331 run_item_main_thread!(self, |self_: Self| {
332 (*self_.0).as_ref().remove(kind.inner().inner_muda())
333 })?
334 .map_err(Into::into)
335 }
336
337 pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {
339 run_item_main_thread!(self, |self_: Self| {
340 (*self_.0)
341 .as_ref()
342 .remove_at(position)
343 .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))
344 })
345 }
346
347 pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>
349 where
350 I: ?Sized,
351 MenuId: PartialEq<&'a I>,
352 {
353 self
354 .items()
355 .unwrap_or_default()
356 .into_iter()
357 .find(|i| i.id() == &id)
358 }
359
360 pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
362 run_item_main_thread!(self, |self_: Self| {
363 (*self_.0)
364 .as_ref()
365 .items()
366 .into_iter()
367 .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))
368 .collect::<Vec<_>>()
369 })
370 }
371
372 pub fn text(&self) -> crate::Result<String> {
374 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())
375 }
376
377 pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
381 let text = text.as_ref().to_string();
382 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))
383 }
384
385 pub fn is_enabled(&self) -> crate::Result<bool> {
387 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())
388 }
389
390 pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
392 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))
393 }
394
395 #[cfg(target_os = "macos")]
400 pub fn set_as_windows_menu_for_nsapp(&self) -> crate::Result<()> {
401 run_item_main_thread!(self, |self_: Self| {
402 (*self_.0).as_ref().set_as_windows_menu_for_nsapp()
403 })?;
404 Ok(())
405 }
406
407 #[cfg(target_os = "macos")]
414 pub fn set_as_help_menu_for_nsapp(&self) -> crate::Result<()> {
415 run_item_main_thread!(self, |self_: Self| {
416 (*self_.0).as_ref().set_as_help_menu_for_nsapp()
417 })?;
418 Ok(())
419 }
420
421 pub fn set_icon(&self, icon: Option<crate::image::Image<'_>>) -> crate::Result<()> {
423 let icon = match icon {
424 Some(i) => Some(i.try_into()?),
425 None => None,
426 };
427 run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_icon(icon))
428 }
429
430 pub fn set_native_icon(&self, _icon: Option<NativeIcon>) -> crate::Result<()> {
436 #[cfg(target_os = "macos")]
437 return run_item_main_thread!(self, |self_: Self| {
438 (*self_.0).as_ref().set_native_icon(_icon.map(Into::into))
439 });
440 #[allow(unreachable_code)]
441 Ok(())
442 }
443}