customizing-tauri-windows について
Tauri v2 開発者向けのウィンドウカスタマイズガイドです。設定ファイル、JavaScript API、Rust API によるカスタムタイトルバー、透明ウィンドウ、メニューの実装を扱います。
主な機能
- 01ウィンドウサイズと装飾の設定
- 02カスタムタイトルバーとドラッグ領域の実装
- 03macOS の透明タイトルバー設定
- 04JavaScript または Rust でのメニュー作成
- 05メニューイベント処理と項目の更新
- 06メニューのキーボードショートカット設定
活用シーン
- 01Tauri アプリのカスタムタイトルバー設計
- 02ウィンドウへの最小化・最大化操作の追加
- 03アプリメニューの構築とイベント処理
- 04メニュー項目の状態やテキストの更新
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dchuk/ claude-code-tauri-skills --skill customizing-tauri-windowsインストール後、Claude Code で/customizing-tauri-windowsを入力して読み込まれたか確認します。
詳細
Tauri ウィンドウのカスタマイズ
Tauri v2 でのウィンドウのカスタマイズについて説明します。カスタムタイトルバー、透過ウィンドウ、ウィンドウメニューを扱います。
設定方法
- tauri.conf.json - ビルド時の静的設定
- JavaScript Window API - フロントエンドからの実行時の変更
- Rust Window struct - バックエンドからの実行時の変更
ウィンドウの設定(tauri.conf.json)
{
"app": {
"windows": [{
"title": "My App",
"width": 800,
"height": 600,
"decorations": true,
"transparent": false,
"alwaysOnTop": false,
"center": true
}]
}
}
カスタムタイトルバーの実装
ステップ 1:装飾を無効にする
{ "app": { "windows": [{ "decorations": false }] } }
ステップ 2:権限を設定する(src-tauri/capabilities/default.json)
{
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:window:default",
"core:window:allow-start-dragging",
"core:window:allow-close",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize"
]
}
ステップ 3:HTML 構造
<div class="titlebar">
<div class="titlebar-drag" data-tauri-drag-region>
<span class="title">My Application</span>
</div>
<div class="titlebar-controls">
<button id="titlebar-minimize">-</button>
<button id="titlebar-maximize">[]</button>
<button id="titlebar-close">x</button>
</div>
</div>
<main class="content"></main>
ステップ 4:CSS スタイリング
.titlebar {
height: 30px;
background: #329ea3;
position: fixed;
top: 0;
left: 0;
right: 0;
display: grid;
grid-template-columns: 1fr auto;
user-select: none;
}
.titlebar-drag {
display: flex;
align-items: center;
padding-left: 12px;
}
.titlebar-controls { display: flex; }
.titlebar-controls button {
width: 46px;
height: 30px;
border: none;
background: transparent;
color: white;
cursor: pointer;
}
.titlebar-controls button:hover { background: rgba(255,255,255,0.1); }
.titlebar-controls button#titlebar-close:hover { background: #e81123; }
.content { margin-top: 30px; padding: 16px; }
ステップ 5:JavaScript コントロール
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
document.getElementById('titlebar-minimize')
?.addEventListener('click', () => appWindow.minimize());
document.getElementById('titlebar-maximize')
?.addEventListener('click', () => appWindow.toggleMaximize());
document.getElementById('titlebar-close')
?.addEventListener('click', () => appWindow.close());
ドラッグ領域の動作
data-tauri-drag-region 属性は子要素ではなく、その要素にのみ適用されます。これにより、ボタンの操作性が保たれます。必要に応じて、ドラッグ可能な各子要素に属性を追加してください。
ダブルクリックで最大化する手動ドラッグ
document.getElementById('titlebar')?.addEventListener('mousedown', (e) => {
if (e.buttons === 1 && e.target === e.currentTarget) {
e.detail === 2 ? appWindow.toggleMaximize() : appWindow.startDragging();
}
});
macOS の透過タイトルバー
Cargo.toml
[target."cfg(target_os = \"macos\")".dependencies]
cocoa = "0.26"
Rust での実装
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.title("Transparent Titlebar Window")
.inner_size(800.0, 600.0);
#[cfg(target_os = "macos")]
let win_builder = win_builder.title_bar_style(TitleBarStyle::Transparent);
let window = win_builder.build().unwrap();
#[cfg(target_os = "macos")]
{
use cocoa::appkit::{NSColor, NSWindow};
use cocoa::base::{id, nil};
let ns_window = window.ns_window().unwrap() as id;
unsafe {
let bg_color = NSColor::colorWithRed_green_blue_alpha_(
nil, 50.0/255.0, 158.0/255.0, 163.5/255.0, 1.0
);
ns_window.setBackgroundColor_(bg_color);
}
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application")
}
注記:macOS では、カスタムタイトルバーを使用すると、ウィンドウのスナップなどのネイティブ機能が失われます。透過タイトルバーを使えば、これらの機能を維持できます。
ウィンドウメニュー
メニュー項目の種類
| 種類 | 説明 |
|---|---|
| テキスト | 基本的なラベル付きメニュー項目 |
| チェック | チェック状態を切り替えられる項目 |
| 区切り線 | セクション間の視覚的な区切り |
| アイコン | カスタムアイコン付きの項目(Tauri 2.8.0+) |
メニューの作成(JavaScript/TypeScript)
import { Menu, MenuItem, Submenu, PredefinedMenuItem, CheckMenuItem } from '@tauri-apps/api/menu';
const fileSubmenu = await Submenu.new({
text: 'File',
items: [
await MenuItem.new({
id: 'new', text: 'New', accelerator: 'CmdOrCtrl+N',
action: () => console.log('New')
}),
await MenuItem.new({
id: 'open', text: 'Open', accelerator: 'CmdOrCtrl+O',
action: () => console.log('Open')
}),
await MenuItem.new({
id: 'save', text: 'Save', accelerator: 'CmdOrCtrl+S',
action: () => console.log('Save')
}),
{ type: 'Separator' },
await MenuItem.new({
id: 'quit', text: 'Quit', accelerator: 'CmdOrCtrl+Q',
action: () => console.log('Quit')
})
]
});
const editSubmenu = await Submenu.new({
text: 'Edit',
items: [
await PredefinedMenuItem.new({ item: 'Undo' }),
await PredefinedMenuItem.new({ item: 'Redo' }),
await PredefinedMenuItem.new({ item: 'Separator' }),
await PredefinedMenuItem.new({ item: 'Cut' }),
await PredefinedMenuItem.new({ item: 'Copy' }),
await PredefinedMenuItem.new({ item: 'Paste' })
]
});
const viewSubmenu = await Submenu.new({
text: 'View',
items: [
await CheckMenuItem.new({
id: 'sidebar', text: 'Show Sidebar', checked: true,
action: async (item) => console.log('Sidebar:', await item.isChecked())
})
]
});
const menu = await Menu.new({ items: [fileSubmenu, editSubmenu, viewSubmenu] });
await menu.setAsAppMenu();
メニューの作成(Rust)
use tauri::menu::{MenuBuilder, SubmenuBuilder};
let file_menu = SubmenuBuilder::new(app, "File")
.text("new", "New")
.text("open", "Open")
.text("save", "Save")
.separator()
.text("quit", "Quit")
.build()?;
let edit_menu = SubmenuBuilder::new(app, "Edit")
.undo()
.redo()
.separator()
.cut()
.copy()
.paste()
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_menu, &edit_menu])
.build()?;
app.set_menu(menu)?;
macOS に関する注記:すべてのメニュー項目はサブメニュー内にまとめる必要があります。最上位の項目は無視されます。
メニューイベントの処理(Rust)
app.on_menu_event(|_app_handle, event| {
match event.id().0.as_str() {
"new" => println!("New file"),
"open" => println!("Open file"),
"save" => println!("Save file"),
"quit" => std::process::exit(0),
_ => {}
}
});
メニューの動的更新
JavaScript:
const statusItem = await menu.get('status');
if (statusItem) await statusItem.setText('Status: Ready');
Rust:
menu.get("status").unwrap().as_menuitem_unchecked().set_text("Status: Ready")?;
キーボードショートカット(アクセラレーター)
| ショートカット | アクセラレーター文字列 |
|---|---|
| Ctrl+S / Cmd+S | CmdOrCtrl+S |
| Ctrl+Shift+S | CmdOrCtrl+Shift+S |
| Alt+F4 | Alt+F4 |
| F11 | F11 |
完全な例
main.rs
use tauri::menu::{MenuBuilder, SubmenuBuilder};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let file_menu = SubmenuBuilder::new(app, "File")
.text("new", "New")
.text("open", "Open")
.separator()
.text("quit", "Quit")
.build()?;
let edit_menu = SubmenuBuilder::new(app, "Edit")
.undo().redo().separator().cut().copy().paste()
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_menu, &edit_menu])
.build()?;
app.set_menu(menu)?;
Ok(())
})
.on_menu_event(|_app, event| {
match event.id().0.as_str() {
"quit" => std::process::exit(0),
id => println!("Menu event: {}", id),
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application")
}
React コンポーネント
import { useEffect } from 'react';
import { getCurrentWindow } from '@tauri-apps/api/window';
function App() {
useEffect(() => {
const appWindow = getCurrentWindow();
const minimize = () => appWindow.minimize();
const maximize = () => appWindow.toggleMaximize();
const close = () => appWindow.close();
document.getElementById('titlebar-minimize')?.addEventListener('click', minimize);
document.getElementById('titlebar-maximize')?.addEventListener('click', maximize);
document.getElementById('titlebar-close')?.addEventListener('click', close);
return () => {
document.getElementById('titlebar-minimize')?.removeEventListener('click', minimize);
document.getElementById('titlebar-maximize')?.removeEventListener('click', maximize);
document.getElementById('titlebar-close')?.removeEventListener('click', close);
};
}, []);
return (
<>
<div className="titlebar">
<div className="titlebar-drag" data-tauri-drag-region>
<span>My Tauri App</span>
</div>
<div className="titlebar-controls">
<button id="titlebar-minimize">-</button>
<button id="titlebar-maximize">[]</button>
<button id="titlebar-close">x</button>
</div>
</div>
<main className="content">
<h1>Welcome to Tauri</h1>
</main>
</>
);
}
export default App;
コメント
Markdown 対応