Spot - Go言語向け、Reactに似たデスクトップGUIツールキット
(github.com/roblillack)- Spotは、GoでクロスプラットフォームのデスクトップGUIを作るためのシンプルなリアクティブツールキットで、可能な場合はネイティブウィジェットを使い、プラットフォーム別APIを一貫して提供する
- アプリケーションの状態が変わると、不変のコンポーネントツリーを作り直し、以前の状態と比較して更新すべきUIコントロールを決定する方式で動作する
- 現在のバックエンドは、macOSのCocoaと、それ以外のプラットフォームのFLTKベース実装で、macOSでもオプションでFLTKを使用できる
spotはリアクティブモデルとレンダリングを提供するバックエンド非依存のコアパッケージで、spot/uiは事前に用意されたクロスプラットフォームGUIコントロール集- 自動レイアウト、複数ウィンドウ、モーダルダイアログ、リサイズ可能なウィンドウ、メニューバー、カスタムウィジェット、ネイティブウィジェットへのアクセス、ドラッグ&ドロップ、国際化はまだ提供していない
Spotの目的と基本モデル
- SpotはGo向けのリアクティブGUIツールキットで、利用可能な場所ではネイティブウィジェットを使いながら、複数のプラットフォームで一貫したAPIを提供するよう設計されている
- プロジェクトに単純な依存関係として追加し、Goコードだけを書けば、追加ツールやコード生成なしで自己完結型のネイティブGUIバイナリを作成できる
- 例では、
ui.Init()、spot.MountFn(...)、ui.Run()という流れでウィンドウとボタンを作成し、spot.UseState[int](<https://github.com/roblillack/ctx, 0>)でクリック回数の状態を管理する - ボタンのクリックハンドラは
setCounter(counter + 1)を呼び出し、状態が変わるとボタンのタイトルが"Clicked %d times!"形式に変わる
リアクティブな更新方式
- Spotにおけるreactiveとは、アプリケーションの状態が変わったときにUIが自動で更新されることを意味する
- 状態変更時に不変のコンポーネントツリーを作り直し、以前の状態と高速に比較して、どのUIコントロールを更新するかを決定する
- Webではこのアイデアは一般にvirtual DOMと呼ばれ、Spotはこの概念をGoのデスクトップ環境に持ち込み、Reactに似たGUIライブラリを実装する実験から始まった
- 開発者はUIを手動で更新する代わりに、副作用のないレンダリング関数と
UseStateのようなフックでアプリケーションロジックと状態を管理する
バックエンドとパッケージ構成
- Spotはコンパイル時に実行プラットフォームに合ったバックエンドを自動選択する
- 現在提供されているバックエンドは2つ
- macOSではCocoaバックエンドを使用し、それ以外のプラットフォームではFLTKベースのバックエンドを使用する
- macOSでもオプションでFLTKを使用でき、Windowsサポートの改善は今後の計画として残されている
spotはリアクティブモデルとレンダリング機能を提供するコアパッケージであり、spot.Controlインターフェースを実装する任意のコントロール集合と併用できるspot/uiはSpotと一緒に使える、事前に用意されたクロスプラットフォームGUIコントロールパッケージ
コンポーネント、コントロール、フック
- Reactのようにカスタムフックを作成できる
- 第1引数に
*spot.RenderContextを受け取る関数を作り、spot.UseState、spot.UseEffectなどを呼び出してSpotのライフサイクルに接続する - 慣例として関数名には
Use…プレフィックスを付ける
- 第1引数に
- カスタムコンポーネントは、
spot.Componentインターフェースを実装する構造体として作成できる- このインターフェースは
Render(ctx *spot.RenderContext) spot.Componentという単一メソッドを持つ - こうして作ったコンポーネントは、組み込みコンポーネントと同じ方法で使用できる
- このインターフェースは
- Spotにおけるcomponentは、ビジネスロジックと状態を持つ論理単位
- コンポーネントは他のコンポーネントで構成され、最終的に1つ以上のコントロールとしてレンダリングされる
- controlはUIツリーにマウントされる特殊なコンポーネントで、画面上の視覚要素を表す
- 一般的にはボタン、ラベル、テキスト入力のように、GUIバックエンドのネイティブ実装を基盤とする
- 提供されているウィジェットライブラリとは完全に異なるウィジェットライブラリも使用できる
spot.Componentインターフェースを実装し、ネイティブウィジェットを管理する構造体を作ればよい
spot/uiをCocoaやFLTK以外のバックエンドと併用することは、現在サポートされていない
レンダリングライフサイクルの用語
- Make:
spot.Componentインターフェースを実装する構造体インスタンスを作成する、またはレンダー関数とともにspot.Makeを呼び出して新しいコンポーネントインスタンスを作る過程 - Render: コンポーネントの状態を構成要素に適用し、別のコンポーネントインスタンスを返す過程
- Build: コンポーネントを再帰的にレンダリングしてコントロールツリーを作る過程
spot.Buildにコンポーネントインスタンスを渡すか、spot.BuildFnにレンダー関数を渡して実行する
- Mount: 仮想コントロールツリーから実際のUIコントロールを作る過程
- ツリーノードの
Mountを呼び出すか、spot.Mount、spot.MountFnを使用できる
- ツリーノードの
- Update: マウント済みのコントロールツリーを更新する過程
- ツリーノードの
Updateを呼び出して実行する
- ツリーノードの
現在提供していない機能
- Spotは現在、次の機能を提供していない
-
自動レイアウト
- 複数ウィンドウ
- モーダルダイアログ
- リサイズ可能なウィンドウ
- メニューバー
- カスタムウィジェット
- ネイティブウィジェットへのアクセス
- ドラッグ&ドロップ
- 国際化
-
サポートされるUIコントロール
- Spotはボタン、ラベル、テキスト入力、スライダー、ドロップダウンなど複数のUIコントロールを標準提供する
- サポート状況の表記は、❓ 未実装、🚧 作業中、⚠️ 部分実装、✅ 完了に分かれる
- 完了済みの主なコントロールは次のとおり
- Button: 単純なアクション実行ボタン、
Fl_ButtonとNSButtonを使用 - Checkbox: 2つの選択肢のうち1つを選ぶコントロール、
Fl_Check_ButtonとNSButtonを使用 - Dropdown: 複数項目のうち1つを選ぶドロップダウン、
Fl_ChoiceとNSComboBoxを使用 - Image: ビットマップ画像表示コントロール、
Fl_BoxとカスタムNSButtonを使用 - Label: 編集不可のテキストラベル、
Fl_BoxとNSTextFieldを使用 - ListBox: 単一または複数選択リストコントロール、
Fl_Select_Browser/Fl_Multi_BrowserとNSTableViewを使用 - ProgressBar: 長時間処理の進捗表示、
Fl_ProgressとNSProgressIndicatorを使用 - Slider: 水平スライダー入力、
Fl_SliderとNSSliderを使用 - Spinner: 上下ボタン付きの数値入力、
Fl_SpinnerとNSTextField+NSStepperを使用 - TextField: 1行テキスト入力、
Fl_InputとNSTextFieldを使用 - TextEditor: 複数行テキスト編集、
Fl_Text_EditorとNSTextViewを使用 - Window: トップレベルウィンドウコントロール、
Fl_WindowとNSWindowを使用
- Button: 単純なアクション実行ボタン、
- 部分実装または未実装のコントロールもある
- Dial: 円形の状態コントロールで、⚠️ 部分実装の状態
- ComboBox: テキスト入力を組み合わせたドロップダウンメニューで、まだ着手されていない
- 将来的なバックエンド候補として、ネイティブWindowsコントロールライブラリ
https://github.com/rodrigocfd/windigoが挙げられている
1件のコメント
Hacker News のコメント
これはぜひ見ておきたい。Go で社内開発ツールを作るシンプルな方法を探していたところで、実際にはボタンとテキストフィールドがあるフォーム程度のもの
Gio も使ってみたが理解しづらく、今は wails を使っていて、こちらのほうがずっと気に入っている。このプロジェクトも興味深く、見てみる価値がありそう
「クロスプラットフォーム: FLTK[1] と Cocoa[2] を活用して、Spot は Mac、Linux、BSD で動作し、将来的にネイティブ Windows 対応を計画している」という方向性は、本気で絞ることを勧める
学んだことは残して将来の柔軟性は確保しつつ、まずは 1つのツールキット でうまくやるのがよい。GUI ツールキット、GUI バインディング、GUI そのものは、もともと細部にはまり込んで身動きが取れなくなりがちなのに、複数の基盤ツールキットの細部まで自分から背負い込むと、結局どのツールキットもきちんと扱えなくなる可能性が高い
「最初の 90% が作業の 90% で、残りの 10% がさらに別の 90%」という言葉があるが、GUI ではそれすら楽観的に見えるほど。最初の 10% が作業の 90% で、次の 10% はその 10倍、さらに次の 10% もまた 10倍になる。クロスプラットフォーム に挑むと首が締まるかもしれない
今すぐこの主張に同意してくれるとは思わないが、いずれ 3つのツールキットがリッチテキスト処理のようなものを互いに矛盾する 3通りの方法で強制してくる状況に直面したら、最もよくサポートされている、あるいは最も人気のある基盤ツールキットだけを残し、残りは捨ててよいと自分に許可してほしい
Go でこういうものをしばらく探していた。Go は ビルドプロセスがシンプル なので、クロスプラットフォーム UI で優れた開発者体験を提供できる大きな機会があると思う
経験上、クロスプラットフォーム開発の苦痛の半分はビルドの複雑さの管理だが、Go はそれをほぼなくしてくれる
ただ、ネイティブコントロールのデフォルトサイズはプラットフォームごとに違うので、クロスプラットフォームレイアウト をどう解決するのか気になる。この部分は他のクロスプラットフォームツールキットでも、とてもよく解決されているのを見たことがない。それでも幸運を祈る
数年前にこういうものを探していた。ただし Windows 対応も必要だった。結局 wxWidgets を使うために C++ に移り、小さな自己完結型バイナリを得られた
ネイティブツールキットなのに、FLTK がブラウザのように Ctrl-+ と Ctrl+- でアプリ全体の拡大・縮小をサポートしているのを見て感心した。そして https://github.com/fltk-rs/fltk-theme?tab=readme-ov-file#wid... のおかげで、FLTK をどれほど「ネイティブ」っぽく見せられるかについての印象も良くなった
関連して最近 GoVCL https://z-kit.cc/en/ も見つけたので、一度使ってみたい
自己完結型の Spot “Hello World” は自分の Mac で 2.3MiB。美しくはないが、自分には十分動く
ユーザーに表示されるコントロールを直接更新する方式と比べて、仮想コントロールツリー のアプローチにはどんな利点があるのか気になる
あちこちにコールバックコードを書く必要があり、各コールバックは他のすべての活動の現在状態を慎重に確認したうえで、何十個ものウィジェットを更新しなければならないことがある
リアクティブなアプローチ では、与えられた状態に対するインターフェイスを記述する単一のレンダリング関数を書けば、いつ呼び出すか、どんな入力を渡すかはフレームワークが処理してくれる。こちらのほうがずっと理解しやすく、React を使った後では戻りにくいので、Go でも似たことが可能か実験することになった
よさそう。README に 対応プラットフォーム を書いてもらえるか気になる
Windows、Linux、macOS、*BSD、Android、iOS、Web、Tizen といった情報はかなり興味深いと思う
Flutter のドキュメントのように書いてもよさそう: https://docs.flutter.dev/reference/supported-platforms
努力は称賛に値するが、Windows 対応のないクロスプラットフォーム とは?
3週間前にこれを知っていれば、あるいはコミット履歴上、その時点ですでに存在していればよかったのに。ずっと以前から、Go に移植された React や Go 用の React 風フレームワークがあれば開発体験はすごいことになると言ってきたが、これはまさにそれに見える
React.lua で気づくまでは React.js がかなり嫌いだった
最終的に Go 標準の
html/templateだけで似たことをする方法を見つけ、その実装内容をここに書いた: https://www.sheshbabu.com/posts/react-like-composition-using...デスクトップでリリースすると、たいてい Web 版 も欲しくなるのが大きな問題。OS と深く相互作用する非常にニッチなアプリでない限り、特にそうだ
あるいは、モバイルまで含めて複数プラットフォームを対象にする何かが必要になる
かなり長く探したが、最も近かったのは Qt と React Native で、どちらもさまざまな理由で苦痛を伴う選択肢だった
FLTK は Windows をサポートしている。別の解決策を使う計画だから、まだ Windows をサポートしていないのか気になる
ただし自分の目標は、優先度は低いものの Win32 ベースのバックエンドを実装することで、最初の段階は終わっている: https://github.com/roblillack/spot/pull/4