メニュー項目をグレーアウト(無効化)する

Recipe ID: menu-002

メニュー項目の「有効/無効」状態を切り替えて、クリックできないようにグレーアウトする方法を解説します。

ユーザーが特定の操作を行うまで選択できない項目(例:テキストを選択するまで「コピー」を無効にする、保存する変更がない間は「保存」を無効にするなど)を作る場合に便利です。

前提条件

Permissions (権限) の設定

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

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

1. フロントエンドから変更する (TypeScript)

初期状態で無効化する

オブジェクト形式で enabled: false を指定するか、MenuItem.new のオプションで指定します。

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

// オブジェクト形式: 初期状態で無効化
const menu = await Menu.new({
  items: [
    {
      id: 'save',
      text: '保存',
      enabled: false // <--- 無効化された状態で作成
    },
    {
      id: 'copy',
      text: 'コピー',
      enabled: false
    }
  ]
});

await menu.setAsAppMenu();

クラスを使用した記法

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

// 最初から無効化された状態で作成
const saveItem = await MenuItem.new({
  id: 'save',
  text: '保存',
  enabled: false // <--- ここで無効化
});

// メニューを作成してセット
const menu = await Menu.new({
  items: [saveItem]
});
await menu.setAsAppMenu();

動的に有効/無効を切り替える

setEnabled メソッドを使用して、後から状態を変更できます。

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

// メニュー作成時に参照を保持
const menu = await Menu.new({
  items: [
    {
      id: 'save',
      text: '保存',
      enabled: false
    }
  ]
});
await menu.setAsAppMenu();

// 後から状態を変更する場合
const saveItem = await menu.get('save');

if (saveItem) {
  // 有効化 (クリック可能にする)
  await saveItem.setEnabled(true);

  // 再び無効化 (グレーアウトする)
  // await saveItem.setEnabled(false);
}

参考: MenuItem API リファレンス

2. バックエンドから変更する (Rust)

Rust 側でも同様に enabled 引数で初期状態を指定し、set_enabled で変更できます。

初期状態で無効化する

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

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let menu = MenuBuilder::new(app)
                .item(
                    &MenuItemBuilder::with_id("save", "保存")
                        .enabled(false) // 初期状態で無効化
                        .build(app)?
                )
                .build()?;

            app.set_menu(menu)?;

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

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

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

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let handle = app.handle();

            // 1. 初期状態で無効化 (3番目の引数が enabled)
            let save_item = MenuItem::with_id(handle, "save", "保存", false, None::<&str>)?;

            let menu = Menu::with_items(handle, &[&save_item])?;
            app.set_menu(menu)?;

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

動的に有効/無効を切り替える

コマンド内でメニュー項目の状態を変更する例です。

use tauri::{AppHandle, Manager};
use tauri::menu::MenuItem;
use std::sync::Mutex;

// State でメニュー項目を保持する構造体
struct MenuState {
    save_item: MenuItem<tauri::Wry>,
}

#[tauri::command]
fn enable_save_menu(state: tauri::State<'_, Mutex<MenuState>>) -> Result<(), String> {
    let menu_state = state.lock().map_err(|e| e.to_string())?;
    menu_state.save_item.set_enabled(true).map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn disable_save_menu(state: tauri::State<'_, Mutex<MenuState>>) -> Result<(), String> {
    let menu_state = state.lock().map_err(|e| e.to_string())?;
    menu_state.save_item.set_enabled(false).map_err(|e| e.to_string())?;
    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let handle = app.handle();

            // 初期状態で無効化
            let save_item = MenuItem::with_id(handle, "save", "保存", false, None::<&str>)?;

            let menu = tauri::menu::Menu::with_items(handle, &[&save_item])?;
            app.set_menu(menu)?;

            // State として保存
            app.manage(Mutex::new(MenuState { save_item }));

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![enable_save_menu, disable_save_menu])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

補足

* 無効化されたアイテムの見た目: OSによって異なりますが、通常グレーアウトされ、クリックしても反応しません。
* State の活用: Rustで動的に変更する場合は、MenuItem インスタンスを State として保持するのが一般的です。