submenu.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. import {
  5. IconMenuItemOptions,
  6. PredefinedMenuItemOptions,
  7. CheckMenuItemOptions
  8. } from '../menu'
  9. import { MenuItem, type MenuItemOptions } from './menuItem'
  10. import { CheckMenuItem } from './checkMenuItem'
  11. import { IconMenuItem } from './iconMenuItem'
  12. import { PredefinedMenuItem } from './predefinedMenuItem'
  13. import { invoke } from '../core'
  14. import { type LogicalPosition, PhysicalPosition, type Window } from '../window'
  15. import { type ItemKind, MenuItemBase, newMenu } from './base'
  16. import { type MenuOptions } from './menu'
  17. function itemFromKind([rid, id, kind]: [number, string, ItemKind]):
  18. | Submenu
  19. | MenuItem
  20. | PredefinedMenuItem
  21. | CheckMenuItem
  22. | IconMenuItem {
  23. /* eslint-disable @typescript-eslint/no-unsafe-return */
  24. switch (kind) {
  25. case 'Submenu':
  26. // @ts-expect-error constructor is protected for external usage only, safe for us to use
  27. return new Submenu(rid, id)
  28. case 'Predefined':
  29. // @ts-expect-error constructor is protected for external usage only, safe for us to use
  30. return new PredefinedMenuItem(rid, id)
  31. case 'Check':
  32. // @ts-expect-error constructor is protected for external usage only, safe for us to use
  33. return new CheckMenuItem(rid, id)
  34. case 'Icon':
  35. // @ts-expect-error constructor is protected for external usage only, safe for us to use
  36. return new IconMenuItem(rid, id)
  37. case 'MenuItem':
  38. default:
  39. // @ts-expect-error constructor is protected for external usage only, safe for us to use
  40. return new MenuItem(rid, id)
  41. }
  42. /* eslint-enable @typescript-eslint/no-unsafe-return */
  43. }
  44. export type SubmenuOptions = Omit<MenuItemOptions, 'accelerator' | 'action'> &
  45. MenuOptions
  46. /** A type that is a submenu inside a {@linkcode Menu} or {@linkcode Submenu}. */
  47. export class Submenu extends MenuItemBase {
  48. /** @ignore */
  49. protected constructor(rid: number, id: string) {
  50. super(rid, id, 'Submenu')
  51. }
  52. /** Create a new submenu. */
  53. static async new(opts: SubmenuOptions): Promise<Submenu> {
  54. return newMenu('Submenu', opts).then(([rid, id]) => new Submenu(rid, id))
  55. }
  56. /** Returns the text of this submenu. */
  57. async text(): Promise<string> {
  58. return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })
  59. }
  60. /** Sets the text for this submenu. */
  61. async setText(text: string): Promise<void> {
  62. return invoke('plugin:menu|set_text', {
  63. rid: this.rid,
  64. kind: this.kind,
  65. text
  66. })
  67. }
  68. /** Returns whether this submenu is enabled or not. */
  69. async isEnabled(): Promise<boolean> {
  70. return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })
  71. }
  72. /** Sets whether this submenu is enabled or not. */
  73. async setEnabled(enabled: boolean): Promise<void> {
  74. return invoke('plugin:menu|set_enabled', {
  75. rid: this.rid,
  76. kind: this.kind,
  77. enabled
  78. })
  79. }
  80. /**
  81. * Add a menu item to the end of this submenu.
  82. *
  83. * ## Platform-spcific:
  84. *
  85. * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
  86. */
  87. async append<
  88. T extends
  89. | Submenu
  90. | MenuItem
  91. | PredefinedMenuItem
  92. | CheckMenuItem
  93. | IconMenuItem
  94. | MenuItemOptions
  95. | SubmenuOptions
  96. | IconMenuItemOptions
  97. | PredefinedMenuItemOptions
  98. | CheckMenuItemOptions
  99. >(items: T | T[]): Promise<void> {
  100. return invoke('plugin:menu|append', {
  101. rid: this.rid,
  102. kind: this.kind,
  103. items: (Array.isArray(items) ? items : [items]).map((i) =>
  104. 'rid' in i ? [i.rid, i.kind] : i
  105. )
  106. })
  107. }
  108. /**
  109. * Add a menu item to the beginning of this submenu.
  110. *
  111. * ## Platform-spcific:
  112. *
  113. * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
  114. */
  115. async prepend<
  116. T extends
  117. | Submenu
  118. | MenuItem
  119. | PredefinedMenuItem
  120. | CheckMenuItem
  121. | IconMenuItem
  122. | MenuItemOptions
  123. | SubmenuOptions
  124. | IconMenuItemOptions
  125. | PredefinedMenuItemOptions
  126. | CheckMenuItemOptions
  127. >(items: T | T[]): Promise<void> {
  128. return invoke('plugin:menu|prepend', {
  129. rid: this.rid,
  130. kind: this.kind,
  131. items: (Array.isArray(items) ? items : [items]).map((i) =>
  132. 'rid' in i ? [i.rid, i.kind] : i
  133. )
  134. })
  135. }
  136. /**
  137. * Add a menu item to the specified position in this submenu.
  138. *
  139. * ## Platform-spcific:
  140. *
  141. * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
  142. */
  143. async insert<
  144. T extends
  145. | Submenu
  146. | MenuItem
  147. | PredefinedMenuItem
  148. | CheckMenuItem
  149. | IconMenuItem
  150. | MenuItemOptions
  151. | SubmenuOptions
  152. | IconMenuItemOptions
  153. | PredefinedMenuItemOptions
  154. | CheckMenuItemOptions
  155. >(items: T | T[], position: number): Promise<void> {
  156. return invoke('plugin:menu|insert', {
  157. rid: this.rid,
  158. kind: this.kind,
  159. items: (Array.isArray(items) ? items : [items]).map((i) =>
  160. 'rid' in i ? [i.rid, i.kind] : i
  161. ),
  162. position
  163. })
  164. }
  165. /** Remove a menu item from this submenu. */
  166. async remove(
  167. item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
  168. ): Promise<void> {
  169. return invoke('plugin:menu|remove', {
  170. rid: this.rid,
  171. kind: this.kind,
  172. item: [item.rid, item.kind]
  173. })
  174. }
  175. /** Remove a menu item from this submenu at the specified position. */
  176. async removeAt(
  177. position: number
  178. ): Promise<
  179. | Submenu
  180. | MenuItem
  181. | PredefinedMenuItem
  182. | CheckMenuItem
  183. | IconMenuItem
  184. | null
  185. > {
  186. return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', {
  187. rid: this.rid,
  188. kind: this.kind,
  189. position
  190. }).then(itemFromKind)
  191. }
  192. /** Returns a list of menu items that has been added to this submenu. */
  193. async items(): Promise<
  194. Array<
  195. Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
  196. >
  197. > {
  198. return invoke<Array<[number, string, ItemKind]>>('plugin:menu|items', {
  199. rid: this.rid,
  200. kind: this.kind
  201. }).then((i) => i.map(itemFromKind))
  202. }
  203. /** Retrieves the menu item matching the given identifier. */
  204. async get(
  205. id: string
  206. ): Promise<
  207. | Submenu
  208. | MenuItem
  209. | PredefinedMenuItem
  210. | CheckMenuItem
  211. | IconMenuItem
  212. | null
  213. > {
  214. return invoke<[number, string, ItemKind] | null>('plugin:menu|get', {
  215. rid: this.rid,
  216. kind: this.kind,
  217. id
  218. }).then((r) => (r ? itemFromKind(r) : null))
  219. }
  220. /**
  221. * Popup this submenu as a context menu on the specified window.
  222. *
  223. * If the position, is provided, it is relative to the window's top-left corner.
  224. */
  225. async popup(
  226. at?: PhysicalPosition | LogicalPosition,
  227. window?: Window
  228. ): Promise<void> {
  229. let atValue = null
  230. if (at) {
  231. atValue = {} as Record<string, unknown>
  232. atValue[`${at instanceof PhysicalPosition ? 'Physical' : 'Logical'}`] = {
  233. x: at.x,
  234. y: at.y
  235. }
  236. }
  237. return invoke('plugin:menu|popup', {
  238. rid: this.rid,
  239. kind: this.kind,
  240. window: window?.label ?? null,
  241. at: atValue
  242. })
  243. }
  244. /**
  245. * Set this submenu as the Window menu for the application on macOS.
  246. *
  247. * This will cause macOS to automatically add window-switching items and
  248. * certain other items to the menu.
  249. *
  250. * #### Platform-specific:
  251. *
  252. * - **Windows / Linux**: Unsupported.
  253. */
  254. async setAsWindowsMenuForNSApp(): Promise<void> {
  255. return invoke('plugin:menu|set_as_windows_menu_for_nsapp', {
  256. rid: this.rid
  257. })
  258. }
  259. /**
  260. * Set this submenu as the Help menu for the application on macOS.
  261. *
  262. * This will cause macOS to automatically add a search box to the menu.
  263. *
  264. * If no menu is set as the Help menu, macOS will automatically use any menu
  265. * which has a title matching the localized word "Help".
  266. *
  267. * #### Platform-specific:
  268. *
  269. * - **Windows / Linux**: Unsupported.
  270. */
  271. async setAsHelpMenuForNSApp(): Promise<void> {
  272. return invoke('plugin:menu|set_as_help_menu_for_nsapp', {
  273. rid: this.rid
  274. })
  275. }
  276. }