「小さなウィジェットを画面の右上に寄せる」「トレイアイコンの真上にパネルを開く」といった配置を、座標を計算せずに名前で指定できるのが Positioner プラグインです。JS は moveWindow()、Rust は WindowExt の move_window() を使います。画面中央に置くだけなら ウィンドウを画面中央に配置する、任意の座標なら ウィンドウの位置を指定する の方法が向いています。
前提条件
npm run tauri add positioner
変わる場所は プラグインをインストールして有効化する のとおりで、デスクトップ専用なので、クレートは Cargo.toml のデスクトップ向けの節に、権限は capabilities/desktop.json に入ります。positioner:default は moveWindow()・moveWindowConstrained()・handleIconState() を許可するもので、core:default には含まれません。
Tray で始まる位置は、プラグインの tray-icon 機能を有効にしたときだけ使えます。tauri add はこれを書かないので手で直し、トレイに必要な tauri 側の機能も並べます(menu-009)。
[dependencies]
tauri = { version = "2", features = ["tray-icon"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-positioner = { version = "2", features = ["tray-icon"] }
JS のトレイの例まで動かす capability です。moveWindow() が動かすのは呼び出したウィンドウなので、呼ぶページのウィンドウを windows に入れます。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop-capability",
"platforms": ["macOS", "windows", "linux"],
"windows": ["main", "popup"],
"permissions": [
"core:default",
"core:app:allow-default-window-icon",
"core:window:allow-show",
"core:window:allow-hide",
"core:window:allow-set-focus",
"positioner:default"
]
}
指定できる位置は次のとおりです。
| 位置 | 置き方 |
|---|---|
TopLeft TopRight BottomLeft BottomRight | モニターの四隅 |
TopCenter BottomCenter LeftCenter RightCenter | 各辺の中央 |
Center | モニターの中央 |
TrayLeft TrayCenter TrayRight | アイコンのすぐ上(入りきらなければ置き直す) |
TrayBottomLeft TrayBottomCenter TrayBottomRight | ウィンドウの上端をアイコンの上端にそろえる |
Tray 系の横位置は、Center がアイコンと中央をそろえ、Left / Right はアイコンの左端 / 右端から右へ広がります。
基準はモニター全体で、タスクバーなどを除いた作業領域ではありません。画面の下にタスクバーがあると、BottomRight のウィンドウは下端が隠れます。中央寄せには作業領域を基準にする core の center() を、作業領域の角に置くなら workArea から計算して setPosition()(win-005)を使います。
1. フロントエンドから実装する (TypeScript)
画面の角や辺に動かす
引数は行き先だけで、動くのは呼び出したウィンドウです。ほかのウィンドウは、そのページで呼ぶか Rust で動かします。
import { moveWindow, Position } from '@tauri-apps/plugin-positioner';
// <button data-pos="TopRight"> のようなボタンで、このウィンドウを四隅へ寄せる
document.querySelectorAll<HTMLButtonElement>('button[data-pos]').forEach((button) => {
button.addEventListener('click', async () => {
const name = button.dataset.pos as keyof typeof Position;
await moveWindow(Position[name]); // positioner:allow-move-window が必要
});
});
基準は今いるモニターなので、別のモニターの角へは、先に setPosition() でそのモニターへ移してから呼びます。
トレイアイコンの真上にパネルを出す
先に handleIconState() で、アイコンの位置と大きさをプラグインへ渡します。クリックもマウスの出入りも位置を含むので、種類を問わず渡します。一度も渡していないと「Tray position not set」で失敗します。トレイは画面の端にあるので、はみ出す分をモニターの内側へ戻す moveWindowConstrained() を使います。次のコードは、枠なしで "visible": false の popup ウィンドウのページで動かします。
import { TrayIcon, type TrayIconEvent } from '@tauri-apps/api/tray';
import { defaultWindowIcon } from '@tauri-apps/api/app';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { handleIconState, moveWindowConstrained, Position } from '@tauri-apps/plugin-positioner';
const popup = getCurrentWindow();
async function onTrayEvent(event: TrayIconEvent) {
await handleIconState(event); // どの種類のイベントでも、アイコンの位置と大きさを渡す
if (event.type === 'Click' && event.button === 'Left' && event.buttonState === 'Up') {
await moveWindowConstrained(Position.TrayCenter); // アイコンの真上。はみ出す分は内側へ
await popup.show();
await popup.setFocus();
}
}
// ほかの場所をクリックしてフォーカスが外れたら隠す
await popup.onFocusChanged(({ payload: focused }) => {
if (!focused) void popup.hide();
});
if (await TrayIcon.getById('main')) await TrayIcon.removeById('main'); // 再読み込みで増やさない
await TrayIcon.new({
id: 'main',
icon: (await defaultWindowIcon()) ?? undefined,
showMenuOnLeftClick: false,
action: (event) => void onTrayEvent(event),
});
クリックの判定は トレイアイコンがクリックされた時の処理を書く で詳しく扱っています。この JS 版と次の Rust 版は、どちらか一方だけを使います。
2. バックエンドから実装する (Rust)
WindowExt を use すると WebviewWindow で move_window() と move_window_constrained() が使え、ラベルで取り出したどのウィンドウでも動かせます。トレイのイベントはすべて on_tray_event() に渡します(設定ファイルで作ったアイコンなら Builder::on_tray_icon_event() の中で)。tray-icon 機能を有効にしたら、Rust だけで使う場合もプラグインの登録が必要です。起動時の位置を setup で決めてから show() すれば、JS の権限は要らず、別の位置が一瞬見えることもありません。
{
"app": {
"windows": [
{ "label": "main", "title": "My App", "width": 800, "height": 600, "visible": false },
{
"label": "popup",
"url": "popup.html",
"width": 320,
"height": 420,
"visible": false,
"decorations": false,
"resizable": false,
"alwaysOnTop": true,
"skipTaskbar": true
}
]
}
}
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
use tauri::{AppHandle, Manager, WindowEvent};
use tauri_plugin_positioner::{Position, WindowExt};
/// パネルをトレイアイコンの真上に置いて表示する
fn show_panel(app: &AppHandle) -> tauri::Result<()> {
let Some(panel) = app.get_webview_window("popup") else {
return Ok(());
};
panel.move_window_constrained(Position::TrayCenter)?; // はみ出す分はモニターの内側へ
panel.show()?;
panel.set_focus()
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_positioner::init()) // tray-icon 機能を有効にしたら必ず登録する
.setup(|app| {
// "visible": false の main を右上に置いてから表示する
if let Some(main) = app.get_webview_window("main") {
main.move_window(Position::TopRight)?;
main.show()?;
}
TrayIconBuilder::with_id("main")
.icon(tauri::include_image!("icons/32x32.png"))
.show_menu_on_left_click(false)
.on_tray_icon_event(|tray, event| {
// どの種類のイベントでも、アイコンの位置をプラグインへ渡す
tauri_plugin_positioner::on_tray_event(tray.app_handle(), &event);
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up,
..
} = event
{
let _ = show_panel(tray.app_handle());
}
})
.build(app)?;
Ok(())
})
.on_window_event(|window, event| {
// パネルはフォーカスを失ったら隠す
if window.label() == "popup" {
if let WindowEvent::Focused(false) = event {
let _ = window.hide();
}
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
動作確認
npm run tauri dev で起動し、data-pos="BottomRight" のボタンを押すと右下に移ります(下にタスクバーがあれば下端が隠れます)。Rust 版では main が右上に出て、トレイのアイコンを左クリックすると真上にパネルが開き、ほかの場所をクリックすると消えます。起動直後、アイコンにマウスを乗せる前に次を実行すると、コンソールに「Tray position not set」と出ます。
import { moveWindow, Position } from '@tauri-apps/plugin-positioner';
await moveWindow(Position.TrayCenter).catch((e) => console.error(e));
よくあるエラーと対処法
- 「positioner.move_window not allowed. Permissions associated with this command: positioner:allow-move-window, positioner:default」: 呼び出したウィンドウに権限がありません。
desktop.jsonのwindowsにそのラベルがあるかも確かめます。リリースビルドでは「Command plugin:positioner|move_window not allowed by ACL」です。 - 「Tray position not set」: トレイのイベントをまだ受けていないか、
handleIconState()/on_tray_event()を呼んでいません。 - 「invalid args
positionfor commandmove_window」で始まるエラーや「Command move_window_constrained not found」: プラグインのtray-icon機能が無効です。JS のPositionには Tray 系が常にあるので、型チェックでは気付けません。 move_window()で「state() called before manage() for」で始まる panic:tray-icon機能を有効にしたのに、プラグインを登録していません。
OS ごとの違いと注意点
- iOS / Android: 使えません。モバイルもビルドするなら、登録を
#[cfg(desktop)]で囲みます。 - Linux: トレイのイベントが届かないので、Tray 系は「Tray position not set」のままです。画面基準の位置は使えます。
- Tray 系の位置: 上に入りきらないときの置き直し方は OS で違い、トレイが画面の上下どちらにあるかでも収まり方が変わります。配布する OS ごとに確かめ、合わなければ
TrayBottomCenterなどに替えます。 - 倍率の違うモニター: 移った先で物理サイズが変わり、角から少しずれることがあります。もう一度呼べば直ります。
