plugin.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{
  5. collections::HashMap,
  6. sync::{Mutex, MutexGuard},
  7. };
  8. use serde::{Deserialize, Serialize};
  9. use tauri_runtime::window::dpi::Position;
  10. use super::{sealed::ContextMenuBase, *};
  11. use crate::{
  12. command,
  13. image::JsImage,
  14. ipc::{channel::JavaScriptChannelId, Channel},
  15. plugin::{Builder, TauriPlugin},
  16. resources::{ResourceId, ResourceTable},
  17. sealed::ManagerBase,
  18. AppHandle, Manager, RunEvent, Runtime, State, Webview, Window,
  19. };
  20. use tauri_macros::do_menu_item;
  21. #[derive(Deserialize, Serialize)]
  22. pub(crate) enum ItemKind {
  23. Menu,
  24. MenuItem,
  25. Predefined,
  26. Submenu,
  27. Check,
  28. Icon,
  29. }
  30. #[derive(Deserialize)]
  31. #[serde(rename_all = "camelCase")]
  32. pub(crate) struct AboutMetadata {
  33. pub name: Option<String>,
  34. pub version: Option<String>,
  35. pub short_version: Option<String>,
  36. pub authors: Option<Vec<String>>,
  37. pub comments: Option<String>,
  38. pub copyright: Option<String>,
  39. pub license: Option<String>,
  40. pub website: Option<String>,
  41. pub website_label: Option<String>,
  42. pub credits: Option<String>,
  43. pub icon: Option<JsImage>,
  44. }
  45. impl AboutMetadata {
  46. pub fn into_metdata<R: Runtime, M: Manager<R>>(
  47. self,
  48. app: &M,
  49. ) -> crate::Result<super::AboutMetadata<'_>> {
  50. let icon = match self.icon {
  51. Some(i) => Some(i.into_img(app)?.as_ref().clone()),
  52. None => None,
  53. };
  54. Ok(super::AboutMetadata {
  55. name: self.name,
  56. version: self.version,
  57. short_version: self.short_version,
  58. authors: self.authors,
  59. comments: self.comments,
  60. copyright: self.copyright,
  61. license: self.license,
  62. website: self.website,
  63. website_label: self.website_label,
  64. credits: self.credits,
  65. icon,
  66. })
  67. }
  68. }
  69. #[allow(clippy::large_enum_variant)]
  70. #[derive(Deserialize)]
  71. enum Predefined {
  72. Separator,
  73. Copy,
  74. Cut,
  75. Paste,
  76. SelectAll,
  77. Undo,
  78. Redo,
  79. Minimize,
  80. Maximize,
  81. Fullscreen,
  82. Hide,
  83. HideOthers,
  84. ShowAll,
  85. CloseWindow,
  86. Quit,
  87. About(Option<AboutMetadata>),
  88. Services,
  89. }
  90. #[derive(Deserialize)]
  91. struct SubmenuPayload {
  92. id: Option<MenuId>,
  93. text: String,
  94. enabled: Option<bool>,
  95. items: Vec<MenuItemPayloadKind>,
  96. }
  97. impl SubmenuPayload {
  98. pub fn create_item<R: Runtime>(
  99. self,
  100. webview: &Webview<R>,
  101. resources_table: &MutexGuard<'_, ResourceTable>,
  102. ) -> crate::Result<Submenu<R>> {
  103. let mut builder = if let Some(id) = self.id {
  104. SubmenuBuilder::with_id(webview, id, self.text)
  105. } else {
  106. SubmenuBuilder::new(webview, self.text)
  107. };
  108. if let Some(enabled) = self.enabled {
  109. builder = builder.enabled(enabled);
  110. }
  111. for item in self.items {
  112. builder = item.with_item(webview, resources_table, |i| Ok(builder.item(i)))?;
  113. }
  114. builder.build()
  115. }
  116. }
  117. #[derive(Deserialize)]
  118. struct CheckMenuItemPayload {
  119. handler: Option<JavaScriptChannelId>,
  120. id: Option<MenuId>,
  121. text: String,
  122. checked: bool,
  123. enabled: Option<bool>,
  124. accelerator: Option<String>,
  125. }
  126. impl CheckMenuItemPayload {
  127. pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<CheckMenuItem<R>> {
  128. let mut builder = if let Some(id) = self.id {
  129. CheckMenuItemBuilder::with_id(id, self.text)
  130. } else {
  131. CheckMenuItemBuilder::new(self.text)
  132. };
  133. if let Some(accelerator) = self.accelerator {
  134. builder = builder.accelerator(accelerator);
  135. }
  136. if let Some(enabled) = self.enabled {
  137. builder = builder.enabled(enabled);
  138. }
  139. let item = builder.checked(self.checked).build(webview)?;
  140. if let Some(handler) = self.handler {
  141. let handler = handler.channel_on(webview.clone());
  142. webview
  143. .state::<MenuChannels>()
  144. .0
  145. .lock()
  146. .unwrap()
  147. .insert(item.id().clone(), handler);
  148. }
  149. Ok(item)
  150. }
  151. }
  152. #[derive(Deserialize)]
  153. #[serde(untagged)]
  154. enum Icon {
  155. Native(NativeIcon),
  156. Icon(JsImage),
  157. }
  158. #[derive(Deserialize)]
  159. #[serde(rename_all = "camelCase")]
  160. struct IconMenuItemPayload {
  161. handler: Option<JavaScriptChannelId>,
  162. id: Option<MenuId>,
  163. text: String,
  164. icon: Icon,
  165. enabled: Option<bool>,
  166. accelerator: Option<String>,
  167. }
  168. impl IconMenuItemPayload {
  169. pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<IconMenuItem<R>> {
  170. let mut builder = if let Some(id) = self.id {
  171. IconMenuItemBuilder::with_id(id, self.text)
  172. } else {
  173. IconMenuItemBuilder::new(self.text)
  174. };
  175. if let Some(accelerator) = self.accelerator {
  176. builder = builder.accelerator(accelerator);
  177. }
  178. if let Some(enabled) = self.enabled {
  179. builder = builder.enabled(enabled);
  180. }
  181. builder = match self.icon {
  182. Icon::Native(native_icon) => builder.native_icon(native_icon),
  183. Icon::Icon(icon) => builder.icon(icon.into_img(webview)?.as_ref().clone()),
  184. };
  185. let item = builder.build(webview)?;
  186. if let Some(handler) = self.handler {
  187. let handler = handler.channel_on(webview.clone());
  188. webview
  189. .state::<MenuChannels>()
  190. .0
  191. .lock()
  192. .unwrap()
  193. .insert(item.id().clone(), handler);
  194. }
  195. Ok(item)
  196. }
  197. }
  198. #[derive(Deserialize)]
  199. struct MenuItemPayload {
  200. handler: Option<JavaScriptChannelId>,
  201. id: Option<MenuId>,
  202. text: String,
  203. enabled: Option<bool>,
  204. accelerator: Option<String>,
  205. }
  206. impl MenuItemPayload {
  207. pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<MenuItem<R>> {
  208. let mut builder = if let Some(id) = self.id {
  209. MenuItemBuilder::with_id(id, self.text)
  210. } else {
  211. MenuItemBuilder::new(self.text)
  212. };
  213. if let Some(accelerator) = self.accelerator {
  214. builder = builder.accelerator(accelerator);
  215. }
  216. if let Some(enabled) = self.enabled {
  217. builder = builder.enabled(enabled);
  218. }
  219. let item = builder.build(webview)?;
  220. if let Some(handler) = self.handler {
  221. let handler = handler.channel_on(webview.clone());
  222. webview
  223. .state::<MenuChannels>()
  224. .0
  225. .lock()
  226. .unwrap()
  227. .insert(item.id().clone(), handler);
  228. }
  229. Ok(item)
  230. }
  231. }
  232. #[derive(Deserialize)]
  233. struct PredefinedMenuItemPayload {
  234. item: Predefined,
  235. text: Option<String>,
  236. }
  237. impl PredefinedMenuItemPayload {
  238. pub fn create_item<R: Runtime>(
  239. self,
  240. webview: &Webview<R>,
  241. ) -> crate::Result<PredefinedMenuItem<R>> {
  242. match self.item {
  243. Predefined::Separator => PredefinedMenuItem::separator(webview),
  244. Predefined::Copy => PredefinedMenuItem::copy(webview, self.text.as_deref()),
  245. Predefined::Cut => PredefinedMenuItem::cut(webview, self.text.as_deref()),
  246. Predefined::Paste => PredefinedMenuItem::paste(webview, self.text.as_deref()),
  247. Predefined::SelectAll => PredefinedMenuItem::select_all(webview, self.text.as_deref()),
  248. Predefined::Undo => PredefinedMenuItem::undo(webview, self.text.as_deref()),
  249. Predefined::Redo => PredefinedMenuItem::redo(webview, self.text.as_deref()),
  250. Predefined::Minimize => PredefinedMenuItem::minimize(webview, self.text.as_deref()),
  251. Predefined::Maximize => PredefinedMenuItem::maximize(webview, self.text.as_deref()),
  252. Predefined::Fullscreen => PredefinedMenuItem::fullscreen(webview, self.text.as_deref()),
  253. Predefined::Hide => PredefinedMenuItem::hide(webview, self.text.as_deref()),
  254. Predefined::HideOthers => PredefinedMenuItem::hide_others(webview, self.text.as_deref()),
  255. Predefined::ShowAll => PredefinedMenuItem::show_all(webview, self.text.as_deref()),
  256. Predefined::CloseWindow => PredefinedMenuItem::close_window(webview, self.text.as_deref()),
  257. Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),
  258. Predefined::About(metadata) => {
  259. let metadata = match metadata {
  260. Some(m) => Some(m.into_metdata(webview)?),
  261. None => None,
  262. };
  263. PredefinedMenuItem::about(webview, self.text.as_deref(), metadata)
  264. }
  265. Predefined::Services => PredefinedMenuItem::services(webview, self.text.as_deref()),
  266. }
  267. }
  268. }
  269. #[derive(Deserialize)]
  270. #[serde(untagged)]
  271. enum MenuItemPayloadKind {
  272. ExistingItem((ResourceId, ItemKind)),
  273. Predefined(PredefinedMenuItemPayload),
  274. Check(CheckMenuItemPayload),
  275. Submenu(SubmenuPayload),
  276. Icon(IconMenuItemPayload),
  277. MenuItem(MenuItemPayload),
  278. }
  279. impl MenuItemPayloadKind {
  280. pub fn with_item<T, R: Runtime, F: FnOnce(&dyn IsMenuItem<R>) -> crate::Result<T>>(
  281. self,
  282. webview: &Webview<R>,
  283. resources_table: &MutexGuard<'_, ResourceTable>,
  284. f: F,
  285. ) -> crate::Result<T> {
  286. match self {
  287. Self::ExistingItem((rid, kind)) => {
  288. do_menu_item!(resources_table, rid, kind, |i| f(&*i))
  289. }
  290. Self::Submenu(i) => f(&i.create_item(webview, resources_table)?),
  291. Self::Predefined(i) => f(&i.create_item(webview)?),
  292. Self::Check(i) => f(&i.create_item(webview)?),
  293. Self::Icon(i) => f(&i.create_item(webview)?),
  294. Self::MenuItem(i) => f(&i.create_item(webview)?),
  295. }
  296. }
  297. }
  298. #[derive(Deserialize, Default)]
  299. #[serde(rename_all = "camelCase")]
  300. struct NewOptions {
  301. id: Option<MenuId>,
  302. text: Option<String>,
  303. enabled: Option<bool>,
  304. checked: Option<bool>,
  305. accelerator: Option<String>,
  306. #[serde(rename = "item")]
  307. predefined_item: Option<Predefined>,
  308. icon: Option<Icon>,
  309. items: Option<Vec<MenuItemPayloadKind>>,
  310. }
  311. #[command(root = "crate")]
  312. fn new<R: Runtime>(
  313. app: AppHandle<R>,
  314. webview: Webview<R>,
  315. kind: ItemKind,
  316. options: Option<NewOptions>,
  317. channels: State<'_, MenuChannels>,
  318. handler: Channel,
  319. ) -> crate::Result<(ResourceId, MenuId)> {
  320. let options = options.unwrap_or_default();
  321. let mut resources_table = app.resources_table();
  322. let (rid, id) = match kind {
  323. ItemKind::Menu => {
  324. let mut builder = MenuBuilder::new(&app);
  325. if let Some(id) = options.id {
  326. builder = builder.id(id);
  327. }
  328. if let Some(items) = options.items {
  329. for item in items {
  330. builder = item.with_item(&webview, &resources_table, |i| Ok(builder.item(i)))?;
  331. }
  332. }
  333. let menu = builder.build()?;
  334. let id = menu.id().clone();
  335. let rid = resources_table.add(menu);
  336. (rid, id)
  337. }
  338. ItemKind::Submenu => {
  339. let submenu = SubmenuPayload {
  340. id: options.id,
  341. text: options.text.unwrap_or_default(),
  342. enabled: options.enabled,
  343. items: options.items.unwrap_or_default(),
  344. }
  345. .create_item(&webview, &resources_table)?;
  346. let id = submenu.id().clone();
  347. let rid = resources_table.add(submenu);
  348. (rid, id)
  349. }
  350. ItemKind::MenuItem => {
  351. let item = MenuItemPayload {
  352. // handler managed in this function instead
  353. handler: None,
  354. id: options.id,
  355. text: options.text.unwrap_or_default(),
  356. enabled: options.enabled,
  357. accelerator: options.accelerator,
  358. }
  359. .create_item(&webview)?;
  360. let id = item.id().clone();
  361. let rid = resources_table.add(item);
  362. (rid, id)
  363. }
  364. ItemKind::Predefined => {
  365. let item = PredefinedMenuItemPayload {
  366. item: options.predefined_item.unwrap(),
  367. text: options.text,
  368. }
  369. .create_item(&webview)?;
  370. let id = item.id().clone();
  371. let rid = resources_table.add(item);
  372. (rid, id)
  373. }
  374. ItemKind::Check => {
  375. let item = CheckMenuItemPayload {
  376. // handler managed in this function instead
  377. handler: None,
  378. id: options.id,
  379. text: options.text.unwrap_or_default(),
  380. checked: options.checked.unwrap_or_default(),
  381. enabled: options.enabled,
  382. accelerator: options.accelerator,
  383. }
  384. .create_item(&webview)?;
  385. let id = item.id().clone();
  386. let rid = resources_table.add(item);
  387. (rid, id)
  388. }
  389. ItemKind::Icon => {
  390. let item = IconMenuItemPayload {
  391. // handler managed in this function instead
  392. handler: None,
  393. id: options.id,
  394. text: options.text.unwrap_or_default(),
  395. icon: options.icon.unwrap_or(Icon::Native(NativeIcon::User)),
  396. enabled: options.enabled,
  397. accelerator: options.accelerator,
  398. }
  399. .create_item(&webview)?;
  400. let id = item.id().clone();
  401. let rid = resources_table.add(item);
  402. (rid, id)
  403. }
  404. };
  405. channels.0.lock().unwrap().insert(id.clone(), handler);
  406. Ok((rid, id))
  407. }
  408. #[command(root = "crate")]
  409. fn append<R: Runtime>(
  410. webview: Webview<R>,
  411. rid: ResourceId,
  412. kind: ItemKind,
  413. items: Vec<MenuItemPayloadKind>,
  414. ) -> crate::Result<()> {
  415. let resources_table = webview.resources_table();
  416. match kind {
  417. ItemKind::Menu => {
  418. let menu = resources_table.get::<Menu<R>>(rid)?;
  419. for item in items {
  420. item.with_item(&webview, &resources_table, |i| menu.append(i))?;
  421. }
  422. }
  423. ItemKind::Submenu => {
  424. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  425. for item in items {
  426. item.with_item(&webview, &resources_table, |i| submenu.append(i))?;
  427. }
  428. }
  429. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  430. };
  431. Ok(())
  432. }
  433. #[command(root = "crate")]
  434. fn prepend<R: Runtime>(
  435. webview: Webview<R>,
  436. rid: ResourceId,
  437. kind: ItemKind,
  438. items: Vec<MenuItemPayloadKind>,
  439. ) -> crate::Result<()> {
  440. let resources_table = webview.resources_table();
  441. match kind {
  442. ItemKind::Menu => {
  443. let menu = resources_table.get::<Menu<R>>(rid)?;
  444. for item in items {
  445. item.with_item(&webview, &resources_table, |i| menu.prepend(i))?;
  446. }
  447. }
  448. ItemKind::Submenu => {
  449. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  450. for item in items {
  451. item.with_item(&webview, &resources_table, |i| submenu.prepend(i))?;
  452. }
  453. }
  454. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  455. };
  456. Ok(())
  457. }
  458. #[command(root = "crate")]
  459. fn insert<R: Runtime>(
  460. webview: Webview<R>,
  461. rid: ResourceId,
  462. kind: ItemKind,
  463. items: Vec<MenuItemPayloadKind>,
  464. mut position: usize,
  465. ) -> crate::Result<()> {
  466. let resources_table = webview.resources_table();
  467. match kind {
  468. ItemKind::Menu => {
  469. let menu = resources_table.get::<Menu<R>>(rid)?;
  470. for item in items {
  471. item.with_item(&webview, &resources_table, |i| menu.insert(i, position))?;
  472. position += 1
  473. }
  474. }
  475. ItemKind::Submenu => {
  476. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  477. for item in items {
  478. item.with_item(&webview, &resources_table, |i| submenu.insert(i, position))?;
  479. position += 1
  480. }
  481. }
  482. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  483. };
  484. Ok(())
  485. }
  486. #[command(root = "crate")]
  487. fn remove<R: Runtime>(
  488. app: AppHandle<R>,
  489. rid: ResourceId,
  490. kind: ItemKind,
  491. item: (ResourceId, ItemKind),
  492. ) -> crate::Result<()> {
  493. let resources_table = app.resources_table();
  494. let (item_rid, item_kind) = item;
  495. match kind {
  496. ItemKind::Menu => {
  497. let menu = resources_table.get::<Menu<R>>(rid)?;
  498. do_menu_item!(resources_table, item_rid, item_kind, |i| menu.remove(&*i))?;
  499. }
  500. ItemKind::Submenu => {
  501. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  502. do_menu_item!(resources_table, item_rid, item_kind, |i| submenu
  503. .remove(&*i))?;
  504. }
  505. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  506. };
  507. Ok(())
  508. }
  509. macro_rules! make_item_resource {
  510. ($resources_table:ident, $item:ident) => {{
  511. let id = $item.id().clone();
  512. let (rid, kind) = match $item {
  513. MenuItemKind::MenuItem(i) => ($resources_table.add(i), ItemKind::MenuItem),
  514. MenuItemKind::Submenu(i) => ($resources_table.add(i), ItemKind::Submenu),
  515. MenuItemKind::Predefined(i) => ($resources_table.add(i), ItemKind::Predefined),
  516. MenuItemKind::Check(i) => ($resources_table.add(i), ItemKind::Check),
  517. MenuItemKind::Icon(i) => ($resources_table.add(i), ItemKind::Icon),
  518. };
  519. (rid, id, kind)
  520. }};
  521. }
  522. #[command(root = "crate")]
  523. fn remove_at<R: Runtime>(
  524. app: AppHandle<R>,
  525. rid: ResourceId,
  526. kind: ItemKind,
  527. position: usize,
  528. ) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {
  529. let mut resources_table = app.resources_table();
  530. match kind {
  531. ItemKind::Menu => {
  532. let menu = resources_table.get::<Menu<R>>(rid)?;
  533. if let Some(item) = menu.remove_at(position)? {
  534. return Ok(Some(make_item_resource!(resources_table, item)));
  535. }
  536. }
  537. ItemKind::Submenu => {
  538. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  539. if let Some(item) = submenu.remove_at(position)? {
  540. return Ok(Some(make_item_resource!(resources_table, item)));
  541. }
  542. }
  543. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  544. };
  545. Ok(None)
  546. }
  547. #[command(root = "crate")]
  548. fn items<R: Runtime>(
  549. app: AppHandle<R>,
  550. rid: ResourceId,
  551. kind: ItemKind,
  552. ) -> crate::Result<Vec<(ResourceId, MenuId, ItemKind)>> {
  553. let mut resources_table = app.resources_table();
  554. let items = match kind {
  555. ItemKind::Menu => resources_table.get::<Menu<R>>(rid)?.items()?,
  556. ItemKind::Submenu => resources_table.get::<Submenu<R>>(rid)?.items()?,
  557. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  558. };
  559. Ok(
  560. items
  561. .into_iter()
  562. .map(|i| make_item_resource!(resources_table, i))
  563. .collect::<Vec<_>>(),
  564. )
  565. }
  566. #[command(root = "crate")]
  567. fn get<R: Runtime>(
  568. app: AppHandle<R>,
  569. rid: ResourceId,
  570. kind: ItemKind,
  571. id: MenuId,
  572. ) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {
  573. let mut resources_table = app.resources_table();
  574. match kind {
  575. ItemKind::Menu => {
  576. let menu = resources_table.get::<Menu<R>>(rid)?;
  577. if let Some(item) = menu.get(&id) {
  578. return Ok(Some(make_item_resource!(resources_table, item)));
  579. }
  580. }
  581. ItemKind::Submenu => {
  582. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  583. if let Some(item) = submenu.get(&id) {
  584. return Ok(Some(make_item_resource!(resources_table, item)));
  585. }
  586. }
  587. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  588. };
  589. Ok(None)
  590. }
  591. #[command(root = "crate")]
  592. async fn popup<R: Runtime>(
  593. app: AppHandle<R>,
  594. current_window: Window<R>,
  595. rid: ResourceId,
  596. kind: ItemKind,
  597. window: Option<String>,
  598. at: Option<Position>,
  599. ) -> crate::Result<()> {
  600. let window = window
  601. .map(|w| app.manager().get_window(&w))
  602. .unwrap_or(Some(current_window));
  603. if let Some(window) = window {
  604. let resources_table = app.resources_table();
  605. match kind {
  606. ItemKind::Menu => {
  607. let menu = resources_table.get::<Menu<R>>(rid)?;
  608. menu.popup_inner(window, at)?;
  609. }
  610. ItemKind::Submenu => {
  611. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  612. submenu.popup_inner(window, at)?;
  613. }
  614. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  615. };
  616. }
  617. Ok(())
  618. }
  619. #[command(root = "crate")]
  620. fn create_default<R: Runtime>(app: AppHandle<R>) -> crate::Result<(ResourceId, MenuId)> {
  621. let mut resources_table = app.resources_table();
  622. let menu = Menu::default(&app)?;
  623. let id = menu.id().clone();
  624. let rid = resources_table.add(menu);
  625. Ok((rid, id))
  626. }
  627. #[command(root = "crate")]
  628. async fn set_as_app_menu<R: Runtime>(
  629. app: AppHandle<R>,
  630. rid: ResourceId,
  631. ) -> crate::Result<Option<(ResourceId, MenuId)>> {
  632. let mut resources_table = app.resources_table();
  633. let menu = resources_table.get::<Menu<R>>(rid)?;
  634. if let Some(menu) = menu.set_as_app_menu()? {
  635. let id = menu.id().clone();
  636. let rid = resources_table.add(menu);
  637. return Ok(Some((rid, id)));
  638. }
  639. Ok(None)
  640. }
  641. #[command(root = "crate")]
  642. async fn set_as_window_menu<R: Runtime>(
  643. app: AppHandle<R>,
  644. current_window: Window<R>,
  645. rid: ResourceId,
  646. window: Option<String>,
  647. ) -> crate::Result<Option<(ResourceId, MenuId)>> {
  648. let window = window
  649. .map(|w| app.manager().get_window(&w))
  650. .unwrap_or(Some(current_window));
  651. if let Some(window) = window {
  652. let mut resources_table = app.resources_table();
  653. let menu = resources_table.get::<Menu<R>>(rid)?;
  654. if let Some(menu) = menu.set_as_window_menu(&window)? {
  655. let id = menu.id().clone();
  656. let rid = resources_table.add(menu);
  657. return Ok(Some((rid, id)));
  658. }
  659. }
  660. Ok(None)
  661. }
  662. #[command(root = "crate")]
  663. fn text<R: Runtime>(app: AppHandle<R>, rid: ResourceId, kind: ItemKind) -> crate::Result<String> {
  664. let resources_table = app.resources_table();
  665. do_menu_item!(resources_table, rid, kind, |i| i.text())
  666. }
  667. #[command(root = "crate")]
  668. fn set_text<R: Runtime>(
  669. app: AppHandle<R>,
  670. rid: ResourceId,
  671. kind: ItemKind,
  672. text: String,
  673. ) -> crate::Result<()> {
  674. let resources_table = app.resources_table();
  675. do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))
  676. }
  677. #[command(root = "crate")]
  678. fn is_enabled<R: Runtime>(
  679. app: AppHandle<R>,
  680. rid: ResourceId,
  681. kind: ItemKind,
  682. ) -> crate::Result<bool> {
  683. let resources_table = app.resources_table();
  684. do_menu_item!(resources_table, rid, kind, |i| i.is_enabled(), !Predefined)
  685. }
  686. #[command(root = "crate")]
  687. fn set_enabled<R: Runtime>(
  688. app: AppHandle<R>,
  689. rid: ResourceId,
  690. kind: ItemKind,
  691. enabled: bool,
  692. ) -> crate::Result<()> {
  693. let resources_table = app.resources_table();
  694. do_menu_item!(
  695. resources_table,
  696. rid,
  697. kind,
  698. |i| i.set_enabled(enabled),
  699. !Predefined
  700. )
  701. }
  702. #[command(root = "crate")]
  703. fn set_accelerator<R: Runtime>(
  704. app: AppHandle<R>,
  705. rid: ResourceId,
  706. kind: ItemKind,
  707. accelerator: Option<String>,
  708. ) -> crate::Result<()> {
  709. let resources_table = app.resources_table();
  710. do_menu_item!(
  711. resources_table,
  712. rid,
  713. kind,
  714. |i| i.set_accelerator(accelerator),
  715. !Predefined | !Submenu
  716. )
  717. }
  718. #[command(root = "crate")]
  719. fn set_as_windows_menu_for_nsapp<R: Runtime>(
  720. app: AppHandle<R>,
  721. rid: ResourceId,
  722. ) -> crate::Result<()> {
  723. #[cfg(target_os = "macos")]
  724. {
  725. let resources_table = app.resources_table();
  726. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  727. submenu.set_as_help_menu_for_nsapp()?;
  728. }
  729. let _ = rid;
  730. let _ = app;
  731. Ok(())
  732. }
  733. #[command(root = "crate")]
  734. fn set_as_help_menu_for_nsapp<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<()> {
  735. #[cfg(target_os = "macos")]
  736. {
  737. let resources_table = app.resources_table();
  738. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  739. submenu.set_as_help_menu_for_nsapp()?;
  740. }
  741. let _ = rid;
  742. let _ = app;
  743. Ok(())
  744. }
  745. #[command(root = "crate")]
  746. fn is_checked<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<bool> {
  747. let resources_table = app.resources_table();
  748. let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;
  749. check_item.is_checked()
  750. }
  751. #[command(root = "crate")]
  752. fn set_checked<R: Runtime>(app: AppHandle<R>, rid: ResourceId, checked: bool) -> crate::Result<()> {
  753. let resources_table = app.resources_table();
  754. let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;
  755. check_item.set_checked(checked)
  756. }
  757. #[command(root = "crate")]
  758. fn set_icon<R: Runtime>(
  759. app: AppHandle<R>,
  760. rid: ResourceId,
  761. icon: Option<Icon>,
  762. ) -> crate::Result<()> {
  763. let resources_table = app.resources_table();
  764. let icon_item = resources_table.get::<IconMenuItem<R>>(rid)?;
  765. match icon {
  766. Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
  767. Some(Icon::Icon(icon)) => icon_item.set_icon(Some(icon.into_img(&app)?.as_ref().clone())),
  768. None => {
  769. icon_item.set_icon(None)?;
  770. icon_item.set_native_icon(None)?;
  771. Ok(())
  772. }
  773. }
  774. }
  775. struct MenuChannels(Mutex<HashMap<MenuId, Channel>>);
  776. pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
  777. Builder::new("menu")
  778. .setup(|app, _api| {
  779. app.manage(MenuChannels(Mutex::default()));
  780. Ok(())
  781. })
  782. .on_event(|app, e| {
  783. if let RunEvent::MenuEvent(e) = e {
  784. if let Some(channel) = app.state::<MenuChannels>().0.lock().unwrap().get(&e.id) {
  785. let _ = channel.send(&e.id);
  786. }
  787. }
  788. })
  789. .invoke_handler(crate::generate_handler![
  790. new,
  791. append,
  792. prepend,
  793. insert,
  794. remove,
  795. remove_at,
  796. items,
  797. get,
  798. popup,
  799. create_default,
  800. set_as_app_menu,
  801. set_as_window_menu,
  802. text,
  803. set_text,
  804. is_enabled,
  805. set_enabled,
  806. set_accelerator,
  807. set_as_windows_menu_for_nsapp,
  808. set_as_help_menu_for_nsapp,
  809. is_checked,
  810. set_checked,
  811. set_icon,
  812. ])
  813. .build()
  814. }