menu.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. MenuItemOptions,
  6. SubmenuOptions,
  7. IconMenuItemOptions,
  8. PredefinedMenuItemOptions,
  9. CheckMenuItemOptions
  10. } from '../menu'
  11. import { MenuItem } from './menuItem'
  12. import { CheckMenuItem } from './checkMenuItem'
  13. import { IconMenuItem } from './iconMenuItem'
  14. import { PredefinedMenuItem } from './predefinedMenuItem'
  15. import { Submenu } from './submenu'
  16. import { type LogicalPosition, PhysicalPosition } from '../dpi'
  17. import { type Window } from '../window'
  18. import { invoke } from '../core'
  19. import { type ItemKind, MenuItemBase, newMenu } from './base'
  20. function itemFromKind([rid, id, kind]: [number, string, ItemKind]):
  21. | Submenu
  22. | MenuItem
  23. | PredefinedMenuItem
  24. | CheckMenuItem
  25. | IconMenuItem {
  26. /* eslint-disable @typescript-eslint/no-unsafe-return */
  27. switch (kind) {
  28. case 'Submenu':
  29. // @ts-expect-error constructor is protected for external usage only
  30. return new Submenu(rid, id)
  31. case 'Predefined':
  32. // @ts-expect-error constructor is protected for external usage only
  33. return new PredefinedMenuItem(rid, id)
  34. case 'Check':
  35. // @ts-expect-error constructor is protected for external usage only
  36. return new CheckMenuItem(rid, id)
  37. case 'Icon':
  38. // @ts-expect-error constructor is protected for external usage only
  39. return new IconMenuItem(rid, id)
  40. case 'MenuItem':
  41. default:
  42. // @ts-expect-error constructor is protected for external usage only
  43. return new MenuItem(rid, id)
  44. }
  45. /* eslint-enable @typescript-eslint/no-unsafe-return */
  46. }
  47. /** Options for creating a new menu. */
  48. export interface MenuOptions {
  49. /** Specify an id to use for the new menu. */
  50. id?: string
  51. /** List of items to add to the new menu. */
  52. items?: Array<
  53. | Submenu
  54. | MenuItem
  55. | PredefinedMenuItem
  56. | CheckMenuItem
  57. | IconMenuItem
  58. | MenuItemOptions
  59. | SubmenuOptions
  60. | IconMenuItemOptions
  61. | PredefinedMenuItemOptions
  62. | CheckMenuItemOptions
  63. >
  64. }
  65. /** A type that is either a menu bar on the window
  66. * on Windows and Linux or as a global menu in the menubar on macOS.
  67. */
  68. export class Menu extends MenuItemBase {
  69. /** @ignore */
  70. protected constructor(rid: number, id: string) {
  71. super(rid, id, 'Menu')
  72. }
  73. /** Create a new menu. */
  74. static async new(opts?: MenuOptions): Promise<Menu> {
  75. return newMenu('Menu', opts).then(([rid, id]) => new Menu(rid, id))
  76. }
  77. /** Create a default menu. */
  78. static async default(): Promise<Menu> {
  79. return invoke<[number, string]>('plugin:menu|create_default').then(
  80. ([rid, id]) => new Menu(rid, id)
  81. )
  82. }
  83. /**
  84. * Add a menu item to the end of this menu.
  85. *
  86. * #### Platform-specific:
  87. *
  88. * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
  89. */
  90. async append<
  91. T extends
  92. | Submenu
  93. | MenuItem
  94. | PredefinedMenuItem
  95. | CheckMenuItem
  96. | IconMenuItem
  97. | MenuItemOptions
  98. | SubmenuOptions
  99. | IconMenuItemOptions
  100. | PredefinedMenuItemOptions
  101. | CheckMenuItemOptions
  102. >(items: T | T[]): Promise<void> {
  103. return invoke('plugin:menu|append', {
  104. rid: this.rid,
  105. kind: this.kind,
  106. items: (Array.isArray(items) ? items : [items]).map((i) =>
  107. 'rid' in i ? [i.rid, i.kind] : i
  108. )
  109. })
  110. }
  111. /**
  112. * Add a menu item to the beginning of this menu.
  113. *
  114. * #### Platform-specific:
  115. *
  116. * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
  117. */
  118. async prepend<
  119. T extends
  120. | Submenu
  121. | MenuItem
  122. | PredefinedMenuItem
  123. | CheckMenuItem
  124. | IconMenuItem
  125. | MenuItemOptions
  126. | SubmenuOptions
  127. | IconMenuItemOptions
  128. | PredefinedMenuItemOptions
  129. | CheckMenuItemOptions
  130. >(items: T | T[]): Promise<void> {
  131. return invoke('plugin:menu|prepend', {
  132. rid: this.rid,
  133. kind: this.kind,
  134. items: (Array.isArray(items) ? items : [items]).map((i) =>
  135. 'rid' in i ? [i.rid, i.kind] : i
  136. )
  137. })
  138. }
  139. /**
  140. * Add a menu item to the specified position in this menu.
  141. *
  142. * #### Platform-specific:
  143. *
  144. * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
  145. */
  146. async insert<
  147. T extends
  148. | Submenu
  149. | MenuItem
  150. | PredefinedMenuItem
  151. | CheckMenuItem
  152. | IconMenuItem
  153. | MenuItemOptions
  154. | SubmenuOptions
  155. | IconMenuItemOptions
  156. | PredefinedMenuItemOptions
  157. | CheckMenuItemOptions
  158. >(items: T | T[], position: number): Promise<void> {
  159. return invoke('plugin:menu|insert', {
  160. rid: this.rid,
  161. kind: this.kind,
  162. items: (Array.isArray(items) ? items : [items]).map((i) =>
  163. 'rid' in i ? [i.rid, i.kind] : i
  164. ),
  165. position
  166. })
  167. }
  168. /** Remove a menu item from this menu. */
  169. async remove(
  170. item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
  171. ): Promise<void> {
  172. return invoke('plugin:menu|remove', {
  173. rid: this.rid,
  174. kind: this.kind,
  175. item: [item.rid, item.kind]
  176. })
  177. }
  178. /** Remove a menu item from this menu at the specified position. */
  179. async removeAt(
  180. position: number
  181. ): Promise<
  182. | Submenu
  183. | MenuItem
  184. | PredefinedMenuItem
  185. | CheckMenuItem
  186. | IconMenuItem
  187. | null
  188. > {
  189. return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', {
  190. rid: this.rid,
  191. kind: this.kind,
  192. position
  193. }).then(itemFromKind)
  194. }
  195. /** Returns a list of menu items that has been added to this menu. */
  196. async items(): Promise<
  197. Array<
  198. Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
  199. >
  200. > {
  201. return invoke<Array<[number, string, ItemKind]>>('plugin:menu|items', {
  202. rid: this.rid,
  203. kind: this.kind
  204. }).then((i) => i.map(itemFromKind))
  205. }
  206. /** Retrieves the menu item matching the given identifier. */
  207. async get(
  208. id: string
  209. ): Promise<
  210. | Submenu
  211. | MenuItem
  212. | PredefinedMenuItem
  213. | CheckMenuItem
  214. | IconMenuItem
  215. | null
  216. > {
  217. return invoke<[number, string, ItemKind] | null>('plugin:menu|get', {
  218. rid: this.rid,
  219. kind: this.kind,
  220. id
  221. }).then((r) => (r ? itemFromKind(r) : null))
  222. }
  223. /**
  224. * Popup this menu as a context menu on the specified window.
  225. *
  226. * If the position, is provided, it is relative to the window's top-left corner.
  227. */
  228. async popup(
  229. at?: PhysicalPosition | LogicalPosition,
  230. window?: Window
  231. ): Promise<void> {
  232. return invoke('plugin:menu|popup', {
  233. rid: this.rid,
  234. kind: this.kind,
  235. window: window?.label ?? null,
  236. at
  237. })
  238. }
  239. /**
  240. * Sets the app-wide menu and returns the previous one.
  241. *
  242. * If a window was not created with an explicit menu or had one set explicitly,
  243. * this menu will be assigned to it.
  244. */
  245. async setAsAppMenu(): Promise<Menu | null> {
  246. return invoke<[number, string] | null>('plugin:menu|set_as_app_menu', {
  247. rid: this.rid
  248. }).then((r) => (r ? new Menu(r[0], r[1]) : null))
  249. }
  250. /**
  251. * Sets the window menu and returns the previous one.
  252. *
  253. * #### Platform-specific:
  254. *
  255. * - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one
  256. * window, if you need to set it, use {@linkcode Menu.setAsAppMenu} instead.
  257. */
  258. async setAsWindowMenu(window?: Window): Promise<Menu | null> {
  259. return invoke<[number, string] | null>('plugin:menu|set_as_window_menu', {
  260. rid: this.rid,
  261. window: window?.label ?? null
  262. }).then((r) => (r ? new Menu(r[0], r[1]) : null))
  263. }
  264. }