Tauriとvueでリマインドアプリを作ってみた[2]: メニューバーからアプリを表示

## 今回作るもの
以下記事に続いてリマインドアプリを作っていきます。

https://qiita.com/Procrustes5/items/76e0aa3595a5b47a5767

### なぜウェブアプリでなくてtauriなの？
私は普段Gitifyというアプリを使っていて、Macの画面上段にあるメニューバーから操作できるアプリを作りたいと思いました。

画面にずっと出しておく必要はなく、ちょっとした変化に気づくことができるからです。

今回作ったものは以下のようなイメージです。

![Screen Recording 2024-12-21 at 18.18.54.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754242/988f69fb-d043-7282-e116-2cc8117eebea.gif)

## tl;dr
今回書いたコードを先に貼っておきます。
説明が必要な方は引き続き読んでいただければ！

`src-tauri/src/main.rs`
``` rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::ActivationPolicy;

mod tray;

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_positioner::init())
        .setup(|app| {
            #[cfg(target_os = "macos")]
            {
                tray::init_macos_menu_extra(app.handle())?;
                // Dockアイコンを非表示に
                app.set_activation_policy(ActivationPolicy::Accessory);
            }
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

`src-tauri/src/tray.rs`
``` rs
use tauri::{
    menu::{Menu, MenuItem},
    tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
    Manager, Runtime,
};
use tauri_plugin_positioner::WindowExt;

pub fn init_macos_menu_extra<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
    // メニューアイテムの作成
    let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
    let menu = Menu::with_items(app, &[&quit_item])?;

    // トレイアイコンの設定
    let _ = TrayIconBuilder::with_id("menu_extra")
        .icon(app.default_window_icon().unwrap().clone())
        .icon_as_template(true)
        .menu(&menu)
        .menu_on_left_click(false)
        // メニューイベントのハンドリング
        .on_menu_event(move |app, event| match event.id.as_ref() {
            "quit" => {
                app.exit(0);
            }
            _ => {}
        })
        // トレイアイコンクリックイベントのハンドリング
        .on_tray_icon_event(|tray, event| {
            let app = tray.app_handle();

            tauri_plugin_positioner::on_tray_event(app.app_handle(), &event);

            if let TrayIconEvent::Click {
                button: MouseButton::Left,
                button_state: MouseButtonState::Up,
                ..
            } = event
            {
                if let Some(window) = app.get_webview_window("main") {
                    if !window.is_visible().unwrap_or(false) {
                        let _ = window.move_window(tauri_plugin_positioner::Position::TrayBottomCenter);
                        let _ = window.show();
                        let _ = window.set_focus();
                    } else {
                        let _ = window.hide();
                    }
                }
            }
        })
        .build(app);

    Ok(())
}
```

``` src-tauri/Cargo.toml
[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [ "macos-private-api", "tray-icon"] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-plugin-positioner = { version = "2.0.0-rc", features = ["tray-icon"] }
```

## コード分析
前編でも書いていましたが、思い通りに動くメニューバーアプリを作ることに色々なところで苦戦していました。
tauri v2自体の開発をしている人が少なく、情報が限られているので調べた情報がv1のものだったりしてました。

今回の実現したかったものは以下のようなものです。
1. メニューバーにアイコンが追加される
2. アイコンを左クリックしたらvue.jsのwebviewが表示される
3. アイコンを右クリックしたらtrayが表示される
・`tray`の例
![Screenshot 2024-12-21 at 18.32.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754242/0299a5da-3b2b-6c20-a2ce-b761f79bf484.png)

4. Webview以外のところをクリックするとアプリが非表示になる
5. Dockにはアプリアイコンが表示されない

### 1. メニューバーにアイコンが追加される
``` rs
TrayIconBuilder::with_id("menu_extra")
    .icon(app.default_window_icon().unwrap().clone())
    .icon_as_template(true)
```

まずはTrayIconBuilderを使用してメニューバーアイコンを作成する必要があります。
そのあと`icon_as_template(true)`を書いてあげるとmacOSのメニューバーに適したモノクロアイコンを表示します。

ここで`app.default_window_icon()`はアプリケーションのデフォルトアイコンを使用するという意味です。

### 2. アイコンを左クリックしたらvue.jsのwebviewが表示される
``` rs
.on_tray_icon_event(|tray, event| {
    if let TrayIconEvent::Click {
        button: MouseButton::Left,
        button_state: MouseButtonState::Up,
        ..
    } = event
    {
        if let Some(window) = app.get_webview_window("main") {
            if !window.is_visible().unwrap_or(false) {
                let _ = window.move_window(tauri_plugin_positioner::Position::TrayBottomCenter);
                let _ = window.show();
                let _ = window.set_focus();
            } else {
                let _ = window.hide();
            }
        }
    }
})
```
私はここで公式ドキュメントで書かれている`on_menu_event`を使ってハマりました。
右クリックと左クリックで異なったものを見せるためには、trayメニューではなく、アイコンに対してイベントを検知する`on_tray_icon_event`を使う必要がありました。

次は左クリックイベントを検知する必要があります。TrayIconEventのClickでどんなイベントであったかを判断できるようになっています。

あとはget_webview_window("main")でVue.jsのWebViewウィンドウを出してあげます。

ウィンドウが非表示の場合は以下の記述で解決しました。

> トレイアイコンの真下に移動（TrayBottomCenter）
ウィンドウを表示（show()）
フォーカスを設定（set_focus()）

### 3. アイコンを右クリックしたらtrayが表示される
``` rs
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&quit_item])?;

TrayIconBuilder::with_id("menu_extra")
    .menu(&menu)
    .menu_on_left_click(false)
```
`MenuItem`を使ってアプリを終了する機能を持つ`quit`メニューを作りました。

ここでは右クリックの時のみtrayメニューを出すので`menu_on_left_click(false)`を書いています。

### 4. Webview以外のところをクリックするとアプリが非表示になる
``` rust
if !window.is_visible().unwrap_or(false) {
    // ...
} else {
    let _ = window.hide();
}
```

ここでは2.のところのelseになっています。

Webview外をクリックするとウィンドウを非表示にする処理が実行されます。

### 5. Dockにはアプリアイコンが表示されない
``` rust
// main.rs
.setup(|app| {
    #[cfg(target_os = "macos")]
    {
        tray::init_macos_menu_extra(app.handle())?;
        app.set_activation_policy(ActivationPolicy::Accessory);
    }
    Ok(())
})
```

main.rsに`ActivationPolicy::Accessory`を設定して、macOSのDockにアプリケーションアイコンを表示しないようにしています。

## おわりに
v1とv2の情報が混じってる中で、tauriで開発をするのって本当に大変ですね。
私はRustにも慣れていなくとても苦労しました。

一旦基本的な動きができたことで、後はvueでの開発に集中できそうでワクワクです！

## 出典
Tauri v2 ドキュメント

https://v2.tauri.app/learn/system-tray/

Mastering Menu Bar Apps: Using Rust and Tauri for macOS Development!

https://www.youtube.com/watch?v=eo9iEvmdj28