Golangができること、むしろ﹁得意﹂と言われるものはすでにたくさんあります。
●クロスコンパイルが得意だし依存が少ないバイナリができるから、いろんな環境で使えるコマンドラインツールを書くにはGoがいいよ
●パフォーマンスが高いし文字列処理もやりやすいので、高速なAPIサーバが得意。gRPCでもHTTP/2でも
●Webアプリケーション・フレームワークも増えてきていてウェブサービス作れるよ
●ビルドシステムとパッケージマネージャ内蔵なので、gitから簡単にパッケージをダウンロードしてきたり、◯makeコマンドとか◯runtとか◯owerで消耗しなくて済む
●gopher.jsでJavaScriptにもなる
逆に今まであまり良い解がなくて、﹁Goにはちょっと不向きだね﹂と言われ続けていたのがGUIです。鳴り物入りで出てきたGXUIが開発が止まってしまい、それと同じぐらいにshinyというものが開発がスタートしている、というのが現状です。shinyは本格的に動き始めている感はありますが、まだ﹁今日から使える﹂というものではないように思います。
クロスプラットフォームで言えば、下記のエントリーで紹介されているgo-qmlや、mattn-wareのgo-gtkがあり、Windowsに特化したものだとWALKというのもあります。ネイティブUIを使うライブラリとしてはgithub.com/andlabs/uiというのもありますが、大幅にリライト中だよとREADMEにあります。
●go-qmlをWindowsで使う
とはいえ、せっかくgopher.jsもあることだし、デスクトップだけじゃなくてブラウザにも対応したいし、モバイル対応考えるとランタイムのサイズが大きい(GTKはどのぐらい?)のはちょっと避けたいし・・・と考えるとまだまだGolangのGUIはポテンシャルを秘めている(改善の余地ありな)気がしています。
本エントリーでは3分クッキング方式でGoのGUIの可能性について見ていきましょう。
GUIフレームワークとバックエンドのベクターエンジン
GUIフレームワークの構成要素というと、ボタンクラスがあって、ウインドウクラスがあって・・・というのが思い浮かぶ人が大半でしょう。だいたいどのフレームワークでも似たような感じになっていますし、GUIを開発する人が主に触れるオブジェクトはそのようなレイヤーです。ですが、GUIのフレームワークにはそれとは別の共通点もあります。バックエンドにベクターグラフィックスを扱える独立した描画エンジンがいます。 ●Windows ●GDI+(昔) ●Direct2D(今) ●Mac OS X ●Quartz ●Java ●Java 2D ●Google Chrome/Android/Chrome OS ●Skia ●Qt ●QPainter ●FireFox, GTK+ ●cairo ●BeOS ●BView ボタンクラスがボタン描画のすべてを自前でやらなければならないとなる、実装の手間が大きくなってしまいます。OpenGL/DirectXなどの対象のハードウェアを使ったコードを各部品が自前ですべてやるというのも非現実的です。また、OpenGLもDirectXも、フォントを扱う機能はありません。そのような処理をまとめたのが、これらの描画エンジンです。それぞれのAPIを見比べてみると、どれもこれもびっくりするぐらい機能が似通っています。これらのエンジンがフォントのレンダリングも担っています。アンチエイリアシングもサポートしていますね。この﹁ステートマシンベースの描画システム﹂の元ネタはPostScriptあたりになるんでしょうかね?詳しくは調べてませんが。 GUIの実装をするにはこのようなレンダリングエンジンの実装は必須ですが、クローズドソースだったり、コードの規模がちょっと大きめだったりして、実装するのは大変そうです。そんな時に目に止まったのが以下のエントリーです。 ●OpenGLなUIを作るライブラリ一覧 さまざまなコンパクトなGUIフレームワークが紹介されていますが、重要なポイントは、数多くのGUIがバックエンドにNanoVGを使っている点です。NanoVGとは何か?に関しては以下のエントリーあたりが参考になります。 ●NanoVGでHTML5 Canvas の力をC/C++にも ●NanoVGのソースコードを読んでみた 4000行程度のCで実現されたベクターグラフィックスライブラリ(ただし、TrueTypeフォントのパースや画像のロードは除く)です。これならなんとか移植できそうですよね? はい、こちらにできあがりのものを用意しています。下記のコマンドでインストールできます。$ go get github.com/shibukawa/nanovgo
![スクリーンショット 2015-12-04 11.17.55.png](https://qiita-image-store.s3.amazonaws.com/0/6105/6a23564f-0b8e-b913-aecf-8b1c1506e2e4.png)
さて、GUIも
ベクターグラフィックスが扱えるようになりました。NanoVGoのサンプルのボタンとかテキストボックスは単なる絵なので、操作することはできません。せっかくなので上記で紹介されているものをGoに移植してみましょうかね。いくつか見比べてみましたが、blendishはちょっとアプリケーション側のコードが冗長で長かったので、簡単に使えそうなNanoGUIを移植してみましょう。これは6000行ほどのコードです。 はい、こちらにできあがりのものを用意しています。![スクリーンショット 2015-12-05 12.45.21.png](https://qiita-image-store.s3.amazonaws.com/0/6105/3f55985a-d6a1-d7a1-fd76-a863e1dc57e5.png)
クロスコンパイル
MacからWindowsのアプリがビルドしたい!というのが最初から決めていた要件です。mxeというMinGWのディストリビューションを使いました。Macに入れて動かしましたが、説明によればLinuxでもFreeBSDでも動きそうです。MacPortsのmingwが一向にバージョンアップされないのですが、これで大丈夫。$ git clone https://github.com/mxe/mxe.git
$ cd mxe
$ make MXE_TARGETS='i686-w64-mingw32.static'
あとはこんな感じでクロスコンパイルできます。glfwなどはgo-gl/glfwにソースが入っているので、glfw3を入れておかなくても大丈夫かと思います。
$ CGO_ENABLED=1 GOOS=windows GOARCH=386 CC="i686-w64-mingw32.static-gcc" go build -ldflags="-H windowsgui" -o sample.exe
MacからLinuxとかはどうやるんでしょうかね?WindowsからMac/Linuxのクロスコンパイルもできるならやりたいですが・・・
今すぐアプリを書きたい場合で、日本語対応が必要な方は、go-gl/glfw、goxjs/glfwの代わりに上記のパッケージを使うとWindows/Macなら動きます。将来的にglfw v3.2がリリースされて必要なくなったら削除します。今は下記のコマンドでインストールされる依存パッケージも↑を向いています。
日本語をインライン入力したい!
GUIのツールのテキストボックス、日本語が入らないとしたら使い物にはならないですよね?glfwはWindowsに関しては決定後の文字列はWM_CHARイベントで飛んでくるので、一応受け取ることはできました。Macはダメでしたが、glfw自身に出ているPR#643を適用すると一応マルチバイト文字も受け取れるようになります。どちらも、サロゲートペアを持つUTF-16としてしか取り出せないので、絵文字とかを考えるとUTF-32で受け取れるようにさらなる修正は必要かなとは思いますが・・・ とはいえ、受け取れるだけで編集途中の文字列は表示されません。つまり、どんな文字が今入力されているか、心の目で見る必要があります。IME対応というのは昔から行われており、対応付けのランクも上からon-the-spot(編集中文字列を完璧にアプリが扱える)から、over-the-spot, off-the-spot, root window方式と色々あります。詳しくはMozillaのこのページが参考になります。on-the-spotを実現するには最低限次のような機能が必要かな、と思います。 ●入力中文字列をアプリケーションにコールバックで伝える ●カーソル位置をIMEに伝達(候補ウインドウの位置を決めるのに必要な情報) ●IME ON/OFF ●IME ON/OFF状態を取得 ●入力中文字列をクリア これを修正するには、Cのレイヤーのglfwを直すしかありません。そのためにいろいろな調査が必要です。各環境ごとにAPIは完全に違いますし。 はい、この修正を行った結果をこちらに用意してあります。 ●glfwのIME対応のPR#658 ●go-gl/glfwに日本語対応パッチを当てたgoパッケージ github.comshibukawa/glfw-2 ●goxjs/glfwで↑を使うようにパッチを当てたgoパッケージ github.comshibukawa/glfw![out.gif](https://qiita-image-store.s3.amazonaws.com/0/6105/ca184908-acf4-0f03-ec81-6090c445fd12.gif)
$ go get github.com/shibukawa/nanogui.go
現状の詳細はglfwマルチプラットフォームでのIME対応の困りごとまとめにまとめてあります。X11環境は実装したけどうまく動かず、Waylandの環境を作ってみようと思ったけど起動しなかったり、LinuxやBSDはまだまだ動いているとは言いがたい状況です。ヘルプいただけると助かります。
こういうウィジェットも、Makefileとかビルドプロセスへの組み込みを気にせずに、go getで配布できちゃうあたり、Golangのすごく良い点ですよね。簡単ですが、デバッグ用の機能もちょびっとあります
オリジナルのウィジェットの作成
NanoGUI.goはGoで書かれているので、Goさえ書ける人なら自分でウィジェットをどんどん作れます。実際にボタンとかのコードを見てもらうのが早いかと思います。最低限は以下のコードですね。 ●WidgetImplement
を継承した構造体を作る。
●
New新ウィジェット(parent Widget) *新ウィジェ
ット
なコンストラクタ関数を作る。中で InitWidget
を呼ぶ
●
PreferredSize(self Widget, ctx
*nanovgo.Context) (int, int)
メソッドを定義して適切なサイズを返す
●
Draw(ctx *nanovgo.Context)
を継承して描画させる
●
String() string
を継承する(デバッグ用オプション)
必要に応じて、クリックやマウス操作のイベントハンドラをオーバーライドして使います。サイズのx, yは親要素からの相対座標なので、(0, 0, w, h)で描画しちゃうとずれちゃうので要注意です。
ポップアップする要素は、ウィジェット内でポップアップ用のウィジェットをもう1つ作って、Screen直下にぶら下げます。元のウィジェットがアクティブになったら、ポップアップ用のウィジェットを表示させる/前面に持ってくる、という感じになります。
試しにスピナーウィジェットを作ってみましょう。スピナーウィジェット自体は実体を持たず、起動されると、自分の直上のウインドウ(一部のフローティングウィンドウだけをカバーしたい場合)、もしくはスクリーン(OSのウインドウ)全体をポップアップ用ウィジェットがカバーします。イベントハンドラもブロックしたいですよね?
はい、こちらにできあがりのものを用意しています。
![spinner.gif](https://qiita-image-store.s3.amazonaws.com/0/6105/646ce994-4397-9883-2a6a-faeea2350ab2.gif)
nanogui.SetDebug(true) // ウィジェット境界に赤枠描画
screen.DebugPrint() // ウィジェットのツリーをコンソールに表示
次にやりたいこと
一通り動くGUIのフレームワークはできたのですが、まだまだ改善したい所はいろいろあります。これらの機能は大幅な改造を伴うものも多く、NanoVGo/NanoGUI.goからフォークして、別のパッケージとして実装していきたいと思っているところです。GUIのウィジェット周りの構造は大きくは変えないと思うので、現状のNanoGUI.go用にウィジェットを自作した場合も、簡単に移植はできると思います。
フォント周りの改善
日本語が絡む時に必ず出てくるのが、フォントファイルのサイズの話題です。特に、NanoGUI.goの場合は自前でフォントファイルをパースしてベジェに分解してレンダリングして使っています。ブラウザ版だとそのダウンロードからして大変です。ラベルとかで予め使うとわかっている文字だけを抽出してフォントを小さくするというテクニックもありますが(ウェブフォントでは特に)、テキスト入力で日本語の入力を認めるなら、ある程度のカバレッジを持つフォントを利用する必要があります。ざっとUIで使えそうなグリフでかつTTF形式のフリーで使えるフォントを調べてみました。 ● IPA Pゴシック - 6.2MB ● 小夏フォント - 5.7MB ● Nasuフォント - 5.2MB ● 源真ゴシック - 5.1MB ● VLゴシックP - 4.2MB ● さざなみフォント - 1.7MB ● M+フォント - 1.6MB 源真ゴシックあたりは評判のいいNotoSansのアレンジ版(TTF版)ですし、サイズが許すなら使ってもいいかなと思います。さきほどのスクリーンキャストも源真ゴシックを使っています。ウェブならさざなみかM+が良さそうです。日本語フォントを久々に色々調べてみましたが、昔はベースとして和田研フォントが、少し前はIPAフォントがよく使われていましたが、最近はNoto Sans(というか源ノ角ゴシック)をベースにしたフォントが増えていますね。 デスクトップの場合はOSのフォントを使うのも選択肢に入るでしょう。OSのフォントを使うことができれば、フォントに入ってないグリフが必要になったときのフォールバック処理も可能です。ただし、UIでどのフォントを使うか、フォールバックで使われるフォントの情報などは、アプリが自前でフォントフォルダを探してなんとかするのではなくOSなどが事前にデータベース化している情報を問い合わせる必要があります。node.jsですが、マルチプラットフォームでこれを実現するライブラリがあります。 ●font-manager(node.js) 一部Goに移植して実装してみたのですが、Mac OSだとOpenTypeフォントが代替フォントとして提案されます。残念ながら、今使っているフォントパーサ&レンダラーはTrueTypeフォント限定なので、このままだと使えません。先に行うべきはTrueTypeコレクションとOpenTypeに対応することです。やはりJavaScriptのコードですが、下記のコードは移植するのに良さそうです。 ●opentype.js OpenTypeに対応するメリットとしてはもう1つあって、ウェブフォントが利用できるようになる点です。ウェブフォントであれば、ブラウザでキャッシュされるので、NanoGUI.goを使うアプリが増えてくれば、キャッシュを使いまわしてダウンロードをしないで済むようにするということもできます。 後は絵文字ですね。絵文字はフォールバックの仕組みさえできてしまえば、NotoEmojiのようなTTFフォントとして作られたフォントを入れれば大きな変更を加えなくても表示は可能ですが、これだと単色でしか表示できません。どうせならカラーの絵を表示したいですよね。これもバックエンドの修正が必要です。 ●noto-emojiの画像 ●Twtterのtwemoji あ、あとは禁則処理ですね。Harfbuzzがデファクトスタンダードっぽいけど、これ移植するの大変そう・・・NanoVGoレイヤーを含む性能改善
nanovgも、nanoguiも、キャッシュなどせずに毎フレーム愚直にすべて計算しなおしています。ダーティフラグを持たせて、再計算の必要がなければ一度計算したパスの分解情報を再利用したり、GUIのレイアウトの再利用をすると良さそうだな、と思っています。 あとは、テキストの描画で、三角形を2つ毎回作ってGPUに描画させているのですが、GPU本来の性能を引き出すWebGL頂点データ作成法で書かれているようにTRIANGLE_STRIPを使えばテキストの場合の頂点数が2/3に節約されて良さそうな気がします。文字のUVを事前にGPUに転送しておけば、頂点インデックスを使って文字描画をさらに高速化できそうです・・・ また、今はnanovg外部にAPIとして公開されていない、UV値を直接指定した多角形描画の機能も使えるようなると、アイコンや画像表示のパフォーマンスが上がったり、9パッチが実現しやすくなったりするでしょう。今は画像は一度ペイント要素として展開されてから描画されます。ペイント要素は、一度ステンシルバッファに描画領域の形状をレンダリングし、描画領域全体を覆う要素を作って、ステンシルバッファを見ながら切り抜いて描画する、といった2パス描画になっています。よく使うRect描画ぐらい、1パスでやりたいですよね。フォント描画用のrenderTriangleをちょっと修正するだけなので難しくはないでしょう。フォントの所で触れた絵文字もこの流れで1パスで書ける要素に追加したいですね。 あとは、モバイル対応をするなら、スリープから戻ってきた時にテクスチャを再度GPUに転送しなおす処理も必要ですね。ネイティブ機能の吸収レイヤー
どうしてもOpenGLのレイヤだけではできないこともあります。例えばOS Xのメニューバーにメニューを登録する機能だったり、ファイルやフォントの選択ダイアログだったりといったものです。まだ移植はしてませんが、NanoGUIもダイアログだけはネイティブのダイアログを使うようになっています。 ブラウザ対応する場合のことを考えるとAPIは少し考えないといけないところ。ブラウザだとボタンが押された時しかファイル選択ダイアログが出せなかったりしますし。透明な<input type="fi
le">
を並べておいて、クリックされたらダイアログ表示とかでしょうかね?オリジナルのNanoGUIと違って、FileDialog
Button
みたいな専用のボタンを作る必要がありそうです。保存ダイアログは、環境によってはクリックイベントを介さずにできそうですが、ポータブルな方法はまだなさそうです。
●HTML5のクライアントサイドで作ったデータをサーバーを介さずにファイル保存できるか?