// Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT import { IconMenuItemOptions, PredefinedMenuItemOptions, CheckMenuItemOptions } from '../menu' import { MenuItem, type MenuItemOptions } from './menuItem' import { CheckMenuItem } from './checkMenuItem' import { IconMenuItem } from './iconMenuItem' import { PredefinedMenuItem } from './predefinedMenuItem' import { invoke } from '../core' import { type LogicalPosition, PhysicalPosition, type Window } from '../window' import { type ItemKind, MenuItemBase, newMenu } from './base' import { type MenuOptions } from './menu' function itemFromKind([rid, id, kind]: [number, string, ItemKind]): | Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem { /* eslint-disable @typescript-eslint/no-unsafe-return */ switch (kind) { case 'Submenu': // @ts-expect-error constructor is protected for external usage only, safe for us to use return new Submenu(rid, id) case 'Predefined': // @ts-expect-error constructor is protected for external usage only, safe for us to use return new PredefinedMenuItem(rid, id) case 'Check': // @ts-expect-error constructor is protected for external usage only, safe for us to use return new CheckMenuItem(rid, id) case 'Icon': // @ts-expect-error constructor is protected for external usage only, safe for us to use return new IconMenuItem(rid, id) case 'MenuItem': default: // @ts-expect-error constructor is protected for external usage only, safe for us to use return new MenuItem(rid, id) } /* eslint-enable @typescript-eslint/no-unsafe-return */ } export type SubmenuOptions = Omit & MenuOptions /** A type that is a submenu inside a {@linkcode Menu} or {@linkcode Submenu}. */ export class Submenu extends MenuItemBase { /** @ignore */ protected constructor(rid: number, id: string) { super(rid, id, 'Submenu') } /** Create a new submenu. */ static async new(opts: SubmenuOptions): Promise { return newMenu('Submenu', opts).then(([rid, id]) => new Submenu(rid, id)) } /** Returns the text of this submenu. */ async text(): Promise { return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind }) } /** Sets the text for this submenu. */ async setText(text: string): Promise { return invoke('plugin:menu|set_text', { rid: this.rid, kind: this.kind, text }) } /** Returns whether this submenu is enabled or not. */ async isEnabled(): Promise { return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind }) } /** Sets whether this submenu is enabled or not. */ async setEnabled(enabled: boolean): Promise { return invoke('plugin:menu|set_enabled', { rid: this.rid, kind: this.kind, enabled }) } /** * Add a menu item to the end of this submenu. * * ## Platform-spcific: * * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}. */ async append< T extends | Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem | MenuItemOptions | SubmenuOptions | IconMenuItemOptions | PredefinedMenuItemOptions | CheckMenuItemOptions >(items: T | T[]): Promise { return invoke('plugin:menu|append', { rid: this.rid, kind: this.kind, items: (Array.isArray(items) ? items : [items]).map((i) => 'rid' in i ? [i.rid, i.kind] : i ) }) } /** * Add a menu item to the beginning of this submenu. * * ## Platform-spcific: * * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}. */ async prepend< T extends | Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem | MenuItemOptions | SubmenuOptions | IconMenuItemOptions | PredefinedMenuItemOptions | CheckMenuItemOptions >(items: T | T[]): Promise { return invoke('plugin:menu|prepend', { rid: this.rid, kind: this.kind, items: (Array.isArray(items) ? items : [items]).map((i) => 'rid' in i ? [i.rid, i.kind] : i ) }) } /** * Add a menu item to the specified position in this submenu. * * ## Platform-spcific: * * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}. */ async insert< T extends | Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem | MenuItemOptions | SubmenuOptions | IconMenuItemOptions | PredefinedMenuItemOptions | CheckMenuItemOptions >(items: T | T[], position: number): Promise { return invoke('plugin:menu|insert', { rid: this.rid, kind: this.kind, items: (Array.isArray(items) ? items : [items]).map((i) => 'rid' in i ? [i.rid, i.kind] : i ), position }) } /** Remove a menu item from this submenu. */ async remove( item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem ): Promise { return invoke('plugin:menu|remove', { rid: this.rid, kind: this.kind, item: [item.rid, item.kind] }) } /** Remove a menu item from this submenu at the specified position. */ async removeAt( position: number ): Promise< | Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem | null > { return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', { rid: this.rid, kind: this.kind, position }).then(itemFromKind) } /** Returns a list of menu items that has been added to this submenu. */ async items(): Promise< Array< Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem > > { return invoke>('plugin:menu|items', { rid: this.rid, kind: this.kind }).then((i) => i.map(itemFromKind)) } /** Retrieves the menu item matching the given identifier. */ async get( id: string ): Promise< | Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem | null > { return invoke<[number, string, ItemKind] | null>('plugin:menu|get', { rid: this.rid, kind: this.kind, id }).then((r) => (r ? itemFromKind(r) : null)) } /** * Popup this submenu as a context menu on the specified window. * * If the position, is provided, it is relative to the window's top-left corner. */ async popup( at?: PhysicalPosition | LogicalPosition, window?: Window ): Promise { let atValue = null if (at) { atValue = {} as Record atValue[`${at instanceof PhysicalPosition ? 'Physical' : 'Logical'}`] = { x: at.x, y: at.y } } return invoke('plugin:menu|popup', { rid: this.rid, kind: this.kind, window: window?.label ?? null, at: atValue }) } /** * Set this submenu as the Window menu for the application on macOS. * * This will cause macOS to automatically add window-switching items and * certain other items to the menu. * * #### Platform-specific: * * - **Windows / Linux**: Unsupported. */ async setAsWindowsMenuForNSApp(): Promise { return invoke('plugin:menu|set_as_windows_menu_for_nsapp', { rid: this.rid }) } /** * Set this submenu as the Help menu for the application on macOS. * * This will cause macOS to automatically add a search box to the menu. * * If no menu is set as the Help menu, macOS will automatically use any menu * which has a title matching the localized word "Help". * * #### Platform-specific: * * - **Windows / Linux**: Unsupported. */ async setAsHelpMenuForNSApp(): Promise { return invoke('plugin:menu|set_as_help_menu_for_nsapp', { rid: this.rid }) } }