ビューに関わらない部分はだいたい書き終わった。従って、そろそろビュークラスのことを考える時期だ。ここで、もちろんビュークラスの内部も重要なのだが、どのように再描画を指示するか、そのプロトコルの形式がとても重要だ。
ビューがバッファの内容を表示する際、まず最も基本的に必要なのは
- ビューの基点座標。これはピクセルベースかもしれないし、あるいは行・桁ベースかもしれない。また、ビューの左上が原点かもしれないし、それ以外かもしれないし、あるいは複数原点があるかもしれない
- ビューの幅・高さ。これもピクセルベースかもしれない。いずれにしてもこの情報から派生して、スクリーンに何桁・何文字表示できるかもビューは把握できる
くらいだ。
基本の考え方
何かキー入力がなされ、バッファを更新し、その結果をスクリーンに反映する。これがテキストエディタのライフサイクルなのだけど。この中でスクリーンへの反映を考えるに、まずものすごく原始的には
for (var i = 0; i < SCREEN_LINES; i++) {
display_line_of_buffer(view_top + i);
}
という感じのコードを毎回呼び出すことになる。しかしこれだと、例えば単に 1 文字書き換えただけとか、あるいはもっといえばカーソルを移動しただけで毎回再描画されてしまうので実用にならない。はしょれるところを最大限はしょる必要がある。
最低限の部分だけを再描画する
再描画コードは、バッファが更新された後に呼ばれる。ということは、その時点でバッファの何行目から何行目が更新されたという情報が揃っているはずなのでそれを利用できるようにしよう:
function repaint (start, end) {
for (var i = 0; i < SCREEN_LINES; i++) {
if (view_top + i < start) continue;
if (view_top + i > end) continue;
display_line_of_buffer(view_top + i);
}
}
スクロールに備える
再描画は、バッファが更新された結果だけではなく、単にカーソルが移動しただけでも起こりうる。カーソルがビューの領域から出た場合、カーソルの位置から新しいビューの座標を求め、全体をスクロールさせる必要がある。ここでスクロールが再描画処理の中で独立したトピックなのは、大きく再描画をはしょれる要素だからだ。スクリーンに描画済みで、かつ、スクロール後にもスクリーンにとどまっている行は 1 から再描画を行う必要はなくて、単に描画済みの領域をスクリーン上で移動させるだけでいい。
ただし、描画済みの行であっても、上記の更新済み行に含まれる場合はやはり再描画を行わなければならないので、スクロールの処理と上記の repaint() に相当する処理は同一のループで行う必要がある。再描画することでその 1 行の高さが変化する場合があるからだ。
つまり、
- スクロールが伴わない再描画処理
- カーソルがビューの上方に逃げたために、下方向へスクロールさせつつ再描画する処理
- カーソルがビューの下方に逃げたために、上方向へスクロールさせつつ再描画する処理
の 3 パターンをカーソルの位置によって使い分ける必要がある。さらに、このディスパッチは完全にスクロールと再描画する必要がなくなるまでループさせる必要があるだろう。たとえば更新済み行でスクリーン上部のある行が一気に 100 行分の折り返しを伴う再描画されたとする。そうするとスクリーン下部にあるカーソルは当然追い出されてしまうので、その時点で再度スクロールを伴う再描画を行う必要がある。
マークアップ
そういうわけで再描画を完全なブラウザ任せから一歩脱皮しようとしているわけであるが、結局は HTML の要素を用いたマークアップを用いる。ここ、例えば描画を全て canvas 要素に対して行うようにすればかなりブラウザ独立になるのだけど。ただしそうすると IME を通したインライン入力がちょっと面倒になる。それから、スタイリングが CSS でちょいちょいできないのもめんどくさい。
で。特に上方向にスクロールさせる場合、描画の基準となるカーソル位置がビュー下端になって、そこから上方向に行を配置していくことになるのだがそうなるとこの際、行は position:absolute で配置も自前でやってしまったほうがいいかもしれない。