今回作るもの
以下記事に続いてリマインドアプリを作っていきます。
なぜウェブアプリでなくてtauriなの?
私は普段Gitifyというアプリを使っていて、Macの画面上段にあるメニューバーから操作できるアプリを作りたいと思いました。
画面にずっと出しておく必要はなく、ちょっとした変化に気づくことができるからです。
今回作ったものは以下のようなイメージです。
tl;dr
今回書いたコードを先に貼っておきます。
説明が必要な方は引き続き読んでいただければ!
src-tauri/src/main.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
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(())
}
[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のものだったりしてました。
今回の実現したかったものは以下のようなものです。
-
メニューバーにアイコンが追加される
-
アイコンを左クリックしたらvue.jsのwebviewが表示される
-
Webview以外のところをクリックするとアプリが非表示になる
-
Dockにはアプリアイコンが表示されない
1. メニューバーにアイコンが追加される
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が表示される
.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が表示される
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以外のところをクリックするとアプリが非表示になる
if !window.is_visible().unwrap_or(false) {
// ...
} else {
let _ = window.hide();
}
ここでは2.のところのelseになっています。
Webview外をクリックするとウィンドウを非表示にする処理が実行されます。
5. Dockにはアプリアイコンが表示されない
// 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 ドキュメント
Mastering Menu Bar Apps: Using Rust and Tauri for macOS Development!