DXRubyWS開発秘話

2013年7月26日、大阪に行ったときに暇つぶしにDXRubyWSを検討していたときの文章が出てきたので置いておく。
これで開発されたものがgithubにおいてある。
https://github.com/mirichi/DXRubyWS
見てみたら最初のコミットは7月28日であった。文章を書く前にすでに開発がスタートしていたかどうかについては不明である。よく覚えてない。
そろそろ活動再開せんとなあ。もうちょっと後かなあ。
−−−ここから
ひまなのでちょっと考える。
WindowSystemとしてDXRubyWSを作る。
WSControlをベースとしたクラスツリーを持ち、WSControlはSpriteを継承する。
Spriteを使うので画像イメージはimageにセットする。座標は親オブジェクトの座標を起点とした相対座標で表現する。
下位に複数のWSControlを配置できる親オブジェクトはWSContainerを使う。
WSContainerはimageにRenderTargetをセットし、下位のオブジェクトはtargetを親のRenderTargetにすることで、相対位置で描画することができる。

オブジェクトツリーはWSContainerの下にWSControlもしくはWSContainerを配置していくことで構築する。最上位はWSDesktopで、これはDXRubyのウィンドウのスクリーンを表すSingletonクラスである。
通常、WSDesktopの直下にはWSWindowを配置するが、別にそうしなければならないという決まりはないので直接WSButtonを置いたりもできる。まあ、なんでもおける。

WSWindowはWSContainerを継承して作られていて、WSWindowTitle、WSMenuBar、WSToolBar、WSStatusBar、WSContainerを配置する。それぞれ、ウィンドウのタイトルバー、メニューバー、ツールバー、ステータスバー、クライアント領域を表す。WSWindowはウィンドウぽい動きと外観を作る機能を持ち、ボーダーラインを描画したり、サイズを変更したりできる。WSWindowTitleがあげてくるcloseシグナルを受け取ってクローズできる。

マウスの操作はWindowSystemのメイン処理からWSDesktopのマウスメソッド群を呼び出すことでイベントを起こす。mouse_down/up/moveで、座標とボタンが引数で渡される。WSContainerが受け取った場合、下位のオブジェクトとマウスカーソルの衝突判定を行い、ヒットしたうち最も前面に出ているオブジェクトに対してマウスメソッドの呼び出しをする。この際、コンテナオブジェクトの起点位置からの相対位置をマウスメソッドに渡していく。最終的にWSControlがそれを受け取り処理をすることになる。WS.captureを呼び出すと引数に渡したオブジェクトがキャプチャされ、そのオブジェクトからカーソルが離れてもそのオブジェクトのマウスメソッドが呼ばれ続ける。このとき、該当オブジェクトの絶対位置がわからないので、該当オブジェクトからの相対位置の座標を取得するために、親をたどって絶対座標を算出するメソッドがWSContolに備わっている。

オブジェクトがなんらかの条件を満たした場合のイベントとして、シグナルの機構がある。WSControl#add_handler(obj, signal, handler)というメソッドでシグナルに対するハンドラを登録することができる。
たとえばWSButtonはボタンが押されて離されたタイミングでclickシグナルを発生する。WSControl#signal(:click)が呼ばれて、該当オブジェクトの:clickシグナルに登録されているハンドラを呼び出す動作をする。
add_handlerはobjのsignalにhandlerを登録するので、自身に登録するわけではない。紛らわしいのでなんとかしたほうがよい。
handlerはシンボルの場合はselfの該当メソッドが呼ばれ、MethodオブジェクトやProcオブジェクトの場合はcallされる。まあ、シンボルじゃなかった場合はcallされる。自動的にMethodオブジェクトを作るとあとでメソッドを書き換えても変わらないんじゃないかと思うのでそこは試して考える。
シグナルはそれごとに引数が決められることになるので、まあそれを作った人がインターフェイスをきちんと説明する必要があるだろう。

WSWindowTitleはタイトルバーの背景画像WSImageとタイトルの文字を描画するためのWSLabel、クローズ用ボタンのWSButtonを配置するWSContainerである。WSImageとWSLabelはマウスイベントを受け取らないので何かしら反応するのはボタンだけかと思ったが、WSImageはタイトルバー全体を表しているのでこいつがmoveに反応してくれればウィンドウの移動ができる。WSWindowTitle自身が反応するかどうするかは考えどころである。まあなんしかボタンのclickシグナルをWSWindowTitleのcloseハンドラに設定して、closeハンドラはWSWindowTitleのcloseシグナルを発生させる。これをWSWindowが受け取ることで、クローズ処理をおこなうという感じにしようと考えている。
ウィンドウのリサイズはいまのところボーダーを表すクラスが無いので無理やりWSWindowで処理しようと考えているが、なんかそういういい感じのクラスが作れたらそれで作ればいいんじゃね?って感じではある。
WSBorderみたいな。

この設計はDXRubyのSpriteやRenderTargetの特性を最大限に活かしたものであり、一応Rubyの動的な特性を活かしたシグナル機構も持つ。ウィンドウひとつに大量のRenderTargetを使うことになるので、性能的な部分がどこまでいけるのかというチャレンジにもなっているといえる。まともに動くのかしらね。

ユーザーの操作に対する反応はマウスイベントだが、キーボードはまだ考え中。マウスイベントはWindowSystemが発生させるが、シグナルはオブジェクトが発生させる。
基本的な考え方はアーキテクチャだけ定めておいて、必要なオブジェクトはそれに沿ってユーザが作る。簡単に作れるようにしてある。通常のツールキットは既存のウィジェットを使って作るが、DXRubyWSは超基本のものだけあって、それ以外けはユーザが作る。作れる。
定められているアーキテクチャとしては、WSControl、WSContainerを継承してもらうことと、マウスイベント、シグナル機構の使い方のみである。ほんとは動画とか再生できるといいんだけどね。

夢としては、モジュールとして既存のクラスをWSControl同等に拡張できるものを作って、Kernelにincludeする。既存のすべてのオブジェクトをWSDesktopの下に配置して、なにかしらそれっぽいものを描画することができるようにする。Numericなら数字が表示されるとか、Stringなら文字列が表示されるとか。いわゆるSmalltalk的な「すべてのオブジェクトがそれぞれ意味のある方法で自分自身を表現してみせることができる」。まあ、Rubyにもdisplayメソッドという恐ろしいものがあるのだが。それをASCIIじゃなく、ビジュアル的にやりたいということだ。displayを書き換える。もちろん、Image.displayしたら画面に絵が出る。

ウィンドウをいくつ作っても単一のRubyVMインスタンスであり、Rubyでは存在するすべてのオブジェクトを参照することができるので、オブジェクトブラウザを構築してすべてのオブジェクトを表示していくことができるはず。メソッド一覧とか、インスタンス変数一覧とか。メソッドは勝手に呼ぶのは危ないので表示するだけ、インスタンス変数や定数は中身が参照できるのでそのクラスとdisplayした結果も並べてあげるとよい。そしてそれを書き換えることもできる。

例としてはるびまサンプルSTGをウィンドウ内で動かし、別ウィンドウでマップエディタを動かして、STGの背景マップを別ウィンドウで直接書き換えるようなものを作ってみたい。それそのものに何かしらの意味があるかというと別にないのだが、なんにせよそこにあるデータをなんでも参照して操作できるというその感覚を表現したいわけだ。
マップエディタはマップ配列と画像配列を指定するインターフェイスを作って、オブジェクトブラウザでSTGを覗いてそれを拾ってきてマップを表示、エディタで編集という感じにするとインパクトが伝わるんじゃなかろうか。もちろん変なものを指定するとコケるだろう。
つまり、やって見せるに必要なものは、それらが動くところまでのウィンドウシステムそのものと、オブジェクトブラウザと【これがむずい】、マップエディタである。マップエディタは簡単そうだがオブジェクトブラウザがつらいなー。STGはネームスペースとしてモジュールを作って、その中で動かすようにしておけば、オブジェクトブラウザから探しやすい。なんしかウィンドウシステムで動かすのなら、グローバルネームスペースをむやみに使うのはよろしくない。
オブジェクトブラウザはトップレベルの定数からモジュールを選択して、その中にあるであろうクラス変数@@のオブジェクト配列を覗いて、その中にあるであろうMapクラスが持つマップ配列とイメージ配列を取得するということができなければならない。それを表現するにはツリーを表現しつつその中身を表示するようなコントロールが必要で、なかなか難しそうではある。簡単なのでいいと思うが。

こんなもんかな。ここまで書くのに一時間ぐらいかかった。

たとえば、ウィンドウを丸い絵にしてRenderTargetに自前で描画すると、丸いウィンドウができる。マウスのクリック時に画像をチェックして透明かどうかを見れば、見えている部分しか反応しないということができる。複雑な形状のウィンドウも簡単に作ることができるし、同様に丸いボタンとかも作ることができる。元が簡単なアーキテクチャなだけに、ひらめき一発でたいがいなんでもできるんじゃないか。Spriteだからシェーダ描画も可能だし、頂点シェーダ使えば3D描画もできるはずだ。
たとえばタイトルバーが半透明とかすりガラス風とかもやろうと思えば。

やりたいことは高度なことではない。
シンプルで簡単に拡張でき、DXRubyで可能な表現はそのまま可能で、Rubyの世界を目で見て手でさわれる、つかめるものを作ってみたい。のである。