📖

Tauri(Rust+React+TypeScript) から始めるディスクトップアプリ #4[Menuの実装]

2023/09/04に公開

はじめに

現在、MonacoEditorに入力した文字列を普通のEditorとして動作するようにしていっています。
今回は、Menuを実装します。
最近のアプリケーションは、Menu実装しているは少数だと思いますが、マウス操作をなるべくしたくないのでMenuを実装して、ショートカット操作をわかりやすくしたいです。

Menu の組み込み

Menuは Crate tauri で実装します。
https://docs.rs/tauri/1.4.1/tauri/#
https://docs.rs/tauri/1.4.1/tauri/struct.Menu.html

まず、os_defaultを表示します。
下記の部分を記述しましょう。

main.rs
+ use tauri::{CustomMenuItem, Menu, MenuItem,Submenu};
fn main() {
    ~
+   let context = tauri::generate_context!();
+   let menu = Menu::os_default(&context.package_info().name);
    ~
    tauri::Builder::default()
+       .menu(menu)
        .invoke_handler(tauri::generate_handler![greet,output])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
    ~

サクッと実行

上記のように表示できれば、ひとまず完了です。
Menuこのメニューは、Defaultで用意されているものですが、このままでは要件を満たしていません。

Menu のカストマイズ

os_defaultはあくまで、サンプルみたいなものです。
オーバライトはできるとは思いますが。。。要件にあった独自メニューを作成しましょう。
独自メニューを下記のようにしたいと思います。
※en表記です。日本語つか多言語化は別途考えています。

File
  Project
    Project Create   
    Project Open
  Separator  
  File Open...    Ctrl + o
  File Save       Ctrl + s
  File Save As... Ctrl + Sihft + s
  Separator
  quit            Alt + F4
  Separator
  Exit
Edit
  Undo
  Redo
  Separator
  cut
  

https://docs.rs/tauri/1.4.1/tauri/enum.MenuItem.html

独自メニューは Menu構造体を作成してTauri実行時に指定します。

tauri::Builder::default()
       .menu(menu構造体)

Menu構造体の作成は下記の関数を用いて行います。

  • fn add_native_item  //Tauriが用意しているもの
  • fn add_item      //独自定義のもの
  • fn add_submenu    //サブメニュー作成する場合

fn add_native_item

fn add_native_itemは少し特殊でTauri側で用意しているメニューを追加する関数です。
ショートカットも定義済みなので用意してある場合は使いましょう。
注意点としてnative_itemは、OS毎に異なるので下記の表を参考にしてください。

Name Windows mac lunux etc
About(String, AboutMetadata) - metadata is only applied on Linux
Hide
Services - -
HideOthers - -
ShowAll - -
CloseWindow
Quit
Copy -
Cut -
Undo - -
Redo - -
SelectAll - -
Paste -
EnterFullScreen - -
Minimize
Zoom - -
Separator

プラットフォーム毎にサポート状況が大きく異なるため留意が必要です。
なんだかんだでMac最高!って事ですね)草

fn add_item

CustomMenu構造体に、fn CustomMenuItemを使ってデータを入れます。
作成した、CustomMenu構造体をfn add_itemを使ってMenu構造体に格納します。
CustomMenuItem の構造体は下記のようになっています。

pub struct CustomMenuItem {
  pub id: MenuHash,
  pub id_str: MenuId,
  pub title: String,
  pub keyboard_accelerator: Option<String>,
  pub enabled: bool,
  pub selected: bool,
  #[cfg(target_os = "macos")]
  pub native_image: Option<NativeImage>,
}

id: 一意にして選択時に、イベントを拾います。
title: タイトルです。
keyboard_accelerator: ショートカットの表示をします。
enabled: bool false にすると 選択不可となります。
selected: bool true にすると check が付きます。
pub native_image: Option<NativeImage> mac ならIcon表示できそう。。。

let file_open = CustomMenuItem::new("fileopen","File Open...").accelerator("Ctrl + N").selected().disabled();
let menu = Menu::new().add_item(file_open));

fn add_submenu

サブメニューを作る場合に使います。
ごちゃごちゃ説明するよりコードを参考にしてください。

    let submenu = Submenu::new("File", Menu::new()
                               .add_submenu(menu_project)
                               .add_native_item(MenuItem::Separator)
                               .add_item(file_open)
                               .add_item(file_save)
                               .add_item(file_save_as)
                               .add_native_item(MenuItem::Separator)
                               .add_native_item(MenuItem::Quit));	

サブメニューの中にサブメニューを作成できます。

## Menu構造体
配列の構造体になっています。
Printlnで出来上がった物を見るとわかりやすいので確認してみてください。
サブメニュのサンプルと同じです。初期化後、add_submenu、add_item、add_native_itemにて配列にデータを追加します。簡単に書くと下記のような記述です。

let mut menu = Menu::new();
menu = menu.add_submenu(xxxxxx);
menu = menu.add_item(xxxxx);
menu = menu.add_native_item(xxxx);

サンプルのコード

それではコードです。

fn main() {
    //引数の取得
    let args: Vec<String> = env::args().collect();
    let exefilename = &args[0];
    //let path= &args[1];
    let path:String = exefilename.replace("D4DataStudio.exe", "");
    let logfilename: String =  path + "D4DataStudio.log";

    //Project Menu
    let project_create = CustomMenuItem::new("projectnew", "Project Create",).accelerator("Alt+C");
    let project_open = CustomMenuItem::new("projectopen", "Project Open...",).accelerator("Alt+P");
    let menu_project = Submenu::new("Project",Menu::new()
                                        .add_item(project_create)
                                        .add_item(project_open)
                                    );
    //File menu                                
    let file_open = CustomMenuItem::new("fileopen","File Open...").accelerator("Ctrl + N");
    let file_save = CustomMenuItem::new("filesave","File Save",).accelerator("Ctrl + S");
    let file_save_as = CustomMenuItem::new("filesaveas","File Save As...",).accelerator("Ctrl + Shift + S");
    let file_menu = Submenu::new("File", Menu::new()
                                        .add_submenu(menu_project)
                                        .add_native_item(MenuItem::Separator)
                                        .add_item(file_open)
                                        .add_item(file_save)
                                        .add_item(file_save_as)
                                        .add_native_item(MenuItem::Separator)
                                        .add_native_item(MenuItem::Quit));
    //Edit menu
    let undo = ::new("undo","Undo").accelerator("Ctrl + Z");
    let redo = CustomMenuItem::new("redo","Redo",).accelerator("Ctrl + Y");
    let edit_menu = Submenu::new("Edit", Menu::new()
                                        .add_item(undo)
                                        .add_item(redo)
                                        .add_native_item(MenuItem::Separator)
                                        .add_native_item(MenuItem::Cut)
                                        .add_native_item(MenuItem::Copy)
                                        .add_native_item(MenuItem::Paste));

    let menu = Menu::new()
                        .add_submenu(file_menu)
                        .add_submenu(edit_menu);
    //Debug                                
    println!("{:?}",menu);

    //ロガーの初期化
    logger::init(&logfilename);
    logger::output(logger::Loggerkind::Info, "Logging Initial complete!");

    //Tauri 実行
    tauri::Builder::default()
        .menu(menu)
        .on_menu_event(|event| {
            match event.menu_item_id() {
                "fileopen" => {
                    println!("File_Open :: {:?}",event.menu_item_id());
                }
                "filesave" => {
                    println!("FileSave :: {:?}",event.menu_item_id());
                }
                _ => {
                    println!("未実装 :: {:?}",event.menu_item_id());
                }
            }
        })
        .invoke_handler(tauri::generate_handler![greet,output])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");

on_menu_event にて、eventをフックしています。add_native_itemで定義しているものは、検知できないので注意してください。
また、CustomMenuItem構造体で定義したものは、ショートカットキーが実装できていません。
また、Alt + F にて、Fileメニューを開くようにしたいですが、現状実現できていません。

実行結果

npm run tauri dev

お疲れさまでした。

あとがき

次回はショートカット実装を頑張りたい。

Discussion