アプリケーションのメニューバーを作る

Recipe ID: menu-001

アプリケーションのメニューバーを作る方法を解説します。

Tauri v2 では、Rust バックエンドまたは JavaScript フロントエンドのどちらからでもネイティブメニューを作成できます。

前提条件

Permissions (権限) の設定

src-tauri/capabilities/default.jsonmenu:default 権限を追加します。

{
  "permissions": [
    ...,
    "core:menu:default"
  ]
}

1. フロントエンドから作成する (TypeScript)

Menu.new() を使用してメニューを作成し、setAsAppMenu() でアプリケーション全体のメニューとして設定します。

シンプルな記法(オブジェクト形式)

import { Menu } from '@tauri-apps/api/menu';

const menu = await Menu.new({
  items: [
    {
      text: 'ファイル',
      items: [
        {
          id: 'open',
          text: '開く',
          action: () => {
            console.log('開くがクリックされました');
          }
        },
        {
          id: 'save',
          text: '保存',
          action: () => {
            console.log('保存がクリックされました');
          }
        }
      ]
    }
  ]
});

// アプリケーション全体のメニューとしてセット
await menu.setAsAppMenu();

クラスを使用した記法

import { Menu, Submenu, MenuItem } from '@tauri-apps/api/menu';

// 1. ファイルメニューの項目を作成
const openItem = await MenuItem.new({
  id: 'open',
  text: '開く',
  action: () => {
    console.log('開くがクリックされました');
  }
});

const saveItem = await MenuItem.new({
  id: 'save',
  text: '保存',
  action: () => {
    console.log('保存がクリックされました');
  }
});

// 2. "ファイル" サブメニューを作成し、項目を追加
const fileMenu = await Submenu.new({
  text: 'ファイル',
  items: [openItem, saveItem]
});

// 3. アプリケーションメニューを作成
const menu = await Menu.new({
  items: [fileMenu]
});

// 4. アプリケーション全体のメニューとしてセット
await menu.setAsAppMenu();

参考: Menu API リファレンス

2. バックエンドから作成する (Rust)

Rust 側でメニューを定義し、アプリケーション起動時に設定する方法です。

MenuBuilder を使用した記法(推奨)

use tauri::menu::{MenuBuilder, SubmenuBuilder};

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            // 1. サブメニュー「ファイル」を作成
            // text() メソッドで項目を追加できます
            let file_menu = SubmenuBuilder::new(app, "ファイル")
                .text("open", "開く")
                .text("save", "保存")
                .build()?;

            // 2. アプリケーションメニューを作成し、サブメニューを追加
            let menu = MenuBuilder::new(app)
                .item(&file_menu)
                .build()?;

            // アプリ全体にセット
            app.set_menu(menu)?;

            Ok(())
        })
        .on_menu_event(|app, event| {
            // メニューイベントのハンドリング
            match event.id().0.as_str() {
                "open" => {
                    println!("開くがクリックされました");
                }
                "save" => {
                    println!("保存がクリックされました");
                }
                _ => {}
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

従来の記法(MenuItem を個別に作成)

use tauri::menu::{Menu, MenuItem, Submenu};

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let handle = app.handle();
            
            // 1. メニュー項目の作成
            let open = MenuItem::with_id(handle, "open", "開く", true, None::<&str>)?;
            let save = MenuItem::with_id(handle, "save", "保存", true, None::<&str>)?;
            
            // 2. サブメニューの作成
            let file_menu = Submenu::with_items(handle, "ファイル", true, &[&open, &save])?;
            
            // 3. アプリケーションメニューの作成
            let menu = Menu::with_items(handle, &[&file_menu])?;
            
            // 4. アプリ全体にセット
            app.set_menu(menu)?;

            Ok(())
        })
        .on_menu_event(|app, event| {
            match event.id().0.as_str() {
                "open" => {
                    println!("開くがクリックされました");
                }
                "save" => {
                    println!("保存がクリックされました");
                }
                _ => {}
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

補足

* Rustでの作成: 静的なメニューであれば、Rust側で作成する方が起動時のチラつきがなく、パフォーマンスが良い場合があります。
* Webviewごとの設定: setAsAppMenu() はアプリ全体に適用されますが、setAsWindowMenu() を使うと特定のウィンドウにのみメニューを設定できます (Windows/Linux)。
* macOSの注意: macOS ではグローバルメニューバーとして表示されるため、Menu には Submenu のみを追加できます。
* 初期化タイミング: macOS ではアプリ起動直後にメニューが必要です。React の useEffect や Vue の onMounted など、早い段階で実行してください。