mcpField
C
customizing-tauri-windows

customizing-tauri-windows

github.com/dchukデザイン・UI36221 回インストール40 週間前に更新
リポジトリ

customizing-tauri-windows について

Tauri v2 開発者向けのウィンドウカスタマイズガイドです。設定ファイル、JavaScript API、Rust API によるカスタムタイトルバー、透明ウィンドウ、メニューの実装を扱います。

主な機能

  1. 01ウィンドウサイズと装飾の設定
  2. 02カスタムタイトルバーとドラッグ領域の実装
  3. 03macOS の透明タイトルバー設定
  4. 04JavaScript または Rust でのメニュー作成
  5. 05メニューイベント処理と項目の更新
  6. 06メニューのキーボードショートカット設定

活用シーン

  1. 01Tauri アプリのカスタムタイトルバー設計
  2. 02ウィンドウへの最小化・最大化操作の追加
  3. 03アプリメニューの構築とイベント処理
  4. 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+SCmdOrCtrl+S
Ctrl+Shift+SCmdOrCtrl+Shift+S
Alt+F4Alt+F4
F11F11

完全な例

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;

コメント