🎉

Tauri(Rust+React+TypeScript) から始めるディスクトップアプリ #5[カストマイズWindowの実装]

2023/09/10に公開

はじめに

前回 TauriクレートによるMenuを実装しました。
https://zenn.dev/wara0516/articles/5af7ab30483185

簡単に実装できました。しかし、本当に残念なんですが、自分的に課題があることがわかりました。
個人的に感じたメリデメで解説します。(2023.09.06現在)

メリット

・簡単に実装!! <これは正義>
・制限があるが、マルチプラットフォーム対応できる(コピペのショートカットキーなど)

デメリット

・マウスレス操作を実現できない。(サブメニューをショートカットキーで開けない)
・テーマを選べない(白いメニューとなる)
・メニューバーに別の機能を載せられない。

ディスクトップアプリの場合、ネイティブ感を出す事が大事だと思っています。
ネイティブ感と表現していますが、操作手引やマニュアルを読まなくとも分かる操作であったりUIを実装している事です。WindowsならWindowsの操作ってあると思います。
そのため、操作性やわかりやすいUIデザインが重要となのですが、現状のクレートによるメニューは、機能不足と言わざる負えません。
手軽にサクッと実装できることは、非常に有意義で大事ですが・・・現状だとデメリットの方が大きいので機能改善に期待しましょう。
Electronと比較して、TauriはWindowのカストマイズに関してまだまだな印象があります。V2待ちところがありますが、Electronも最終的には、ヘッドレスで独自Windowデザインを採用すると思いますので、少し手間がかかりますがヘッドレスでWindowカストマイズしていこうと思います。
(注意事項として今回は、Window10 Window11をターゲットとしています。Macで挑戦する場合はこれ以上に設定が必要みたいです。)

カストマイズWindow

Tauriクレートによるタイトルバーやメニューバーを非表示にし、React側の独自実装します。

どんなデザインを目指すのか?

代表的なアプリから参考に考えます。

Excel :タイトルバーとメニューバーの2段構成 WindowsのレガシィなUI

VisualStadioCode :タイトルバーにすべて押込んだUI

今回は、VisualStadioCodeを参考に作成しようと思います。いきなり、全機能を載せられないので
まずは、タイトルバーにアプリアイコンとメニュー縮小アイコン(window-minimize)拡大アイコン(window-maximize)クローズアイコン(close)を表示します。
CSSで書くのはめんどくさいので、取り急ぎTailwindcssを導入します。

Tailwindcssの導入

下記を参考に実施してください。セットアップは難しくないので省略します。
https://tailwindcss.com/docs/installation

DaisyUIの導入

Tailwindcssはカストマイズ性が高く好みですが、皆さん言っている様に
・学習コストが高い。
・お手軽ではない。
muiやChakraを利用することも考えましたが、UIコンポーネントにガッツリ依存することになるため、どれを利用するかは悩みます。選定に時間をかけてられないので、Tailwindowテンプレートで実装を進めます。
選択したのが、DaisyUI。UIコンポーネントような使い方もできますが、Tailwindowでオーバライトできますので、使い勝手が良いのかな?と思います。
https://daisyui.com/

Tauriメニューの非表示

Configを下記のように変更します。

tauri.conf.json

    "windows": [
      {
+       "decorations": false,
        "theme": "Dark",
        "fullscreen": false,
        "resizable": true,
        "title": "D4DataStudio",
        "width": 1280,
        "height": 768,
+       "transparent": true
      }

"decorations": false //メニューが非表示になります。
"transparent": true  //Windowが透明になります。

transparentは、ここで指定するのですが、TailwindowやDaisyUIでIndex.htmlのスタイルを上書きしてしまうので、無効になってしまします。そこで、<style>を追記します。

index.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TEXT</title>
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

<style>
  html,
  body {
+    background-color: transparent !important;
    border-radius: 10px ;
    overflow: hidden;
  }
  .element-inside-your-body {
    overflow: auto;
  }
</style>

<head>タグは、Tauriでは利用されませんので無視でOK。
background-color: transparent !important; tauriで表示されるWindowを透明にしています。
透明になったWindowにReactの画面を表示します。
DaisyUIで優先度が変更されるみたいなので、!importantをつけて強制的に非表示にしています。

タイトルバーの追記

tauriのWindowが透明になったので、独自のタイトルバーをReactで表示します。
ミニマムボタン マックスボタン クローズボタンを追加します。

head.tsx
import { appWindow } from "@tauri-apps/api/window";
import { output } from "./../components/output.ts";

function window_minimize() {
  output("Debug", "window minimize");
  appWindow.minimize();
}
function window_maximize() {
  output("Debug", "window maximize");
  appWindow.toggleMaximize();
}
function window_close() {
  output("Debug", "window close");
  appWindow.close();
}
function Header() {
  return (
    <div className="navbar min-h-8 px-0 py-2 rounded-t-lg  h-8 dark:bg-neutral-800 flex">
      <div data-tauri-drag-region className="flex-none">
        <button className="btn btn-square btn-sm btn-ghost">
          <span className="i-mdi-view-headline w-5 h-5"></span>
        </button>
      </div>
      <div data-tauri-drag-region className="flex-1">
        <a className="btn btn-ghost btn-sm normal-case">D4data</a>
      </div>
      <div data-tauri-drag-region className="flex-none">
        <button
          className="btn btn-ghost btn-sm btn-square"
          onClick={() => window_minimize()}
        >
          <span className="i-mdi-window-minimize  w-5 h-5"></span>
        </button>
        <button
          className="btn btn-ghost btn-sm btn-square"
          onClick={() => window_maximize()}
        >
          <span className="i-mdi-window-maximize  w-5 h-5"></span>
        </button>
        <button
          className="btn btn-ghost btn-sm btn-square"
          onClick={() => window_close()}
        >
          <span className="i-mdi-close  w-5 h-5"></span>
        </button>
      </div>
    </div>
  );
}
export default Header;

上記の結果がこんな感じです。

かなりさみしいですが、これから盛っていきましょう。

気づき

・tauri Menu:Bodyの下に表示されるみたいなので表示しないようにしましょう。
・ショートカット:標準のショートカットは定義済み。動作OK。

調査状況は、下記にまとめています。
https://zenn.dev/wara0516/scraps/e21611a99426a1

終わりに

メニューの実装は、別途やりましょう。

Discussion