昨日はcursesライブラリの基本的な内容について説明した。
今日はウィンドウの生成・削除と描画について。
ウィンドウの生成・削除
ウィンドウは、画面に出力する文字情報を持った矩形の領域。
Cursesでは、ウィンドウに対して文字を追加・削除する操作を行い、その変更を仮想画面、そして画面へと反映させていく。
Cursesではウィンドウを複数作ることが出来る。
また、初期化した段階で、画面全体を覆うデフォルトのウィンドウが作られている。
Cursesモジュールのモジュール関数のいくつかは、このデフォルトのウィンドウを対象としたものとなっている。
デフォルトのウィンドウを取得するには、以下のモジュール関数を使う:
Curses.#stdscr()
画面全体を覆うデフォルトのウィンドウを返す。
デフォルトのウィンドウは、Curses::Windowクラスのインスタンス。
また、ウィンドウを新しく生成したい場合、次のクラスメソッドを使う:
Curses::Window.new(height, width, top, left)
高さがheight
行、幅がwidth
列で、左上の位置が画面のtop
行目、left
列目であるような新しいウィンドウが生成し、そのインスタンスを返す。
(画面の左上が0行0列目)
生成したウィンドウを削除するには、以下のメソッドを使う:
Curses::Window#close()
ウィンドウを削除し、メモリを解放する。
ウィンドウへの文字の追加・削除
各ウィンドウにはカーソルがあり、その位置に対して文字を追加・削除することが出来る。
ウィンドウ内でのカーソル位置を知るには、以下のメソッドを使う:
Curses::Window#cury()
カーソルがウィンドウの何行目にあるかを返す。
(ウィンドウの左上が0行目)
Curses::Window#curx()
カーソルがウィンドウの何列目にあるかを返す。
(ウィンドウの左上が0列目)
カーソル位置を移動するには、次のメソッドを使う:
Curses::Window#setpos(y, x)
カーソル位置をウィンドウのy
行x
列目にする。
(ウィンドウの左上が0行0列目)
ウィンドウに文字を追加するには、以下のメソッドを使うことが出来る:
Curses::Window#addch(ch)
ウィンドウのカーソル位置に文字ch
を上書きし、カーソルを進める。
Curses::Window#addstr(str)
Curses::Window#<<(str)
ウィンドウのカーソル位置に文字列str
を上書きし、カーソルを進める。
Curses::Window#insch(ch)
ウィンドウのカーソル位置に文字ch
を挿入する。
Curses::Window#insertln()
ウィンドウのカーソル位置に一行挿入する。
ウィンドウの文字を削除するには、以下のメソッドを使うことが出来る:
Curses::Window#delch()
ウィンドウのカーソル位置の文字を削除する。
(以降の文字は前に詰められる)
Curses::Window#deleteln()
ウィンドウのカーソル位置の行を削除する。
(以降の行は前に詰められる)
Curses::Window#clear()
ウィンドウの内容をすべて削除する。
Curses::Window#clrtoeol()
ウィンドウのカーソル位置から行末までを削除する。
ウィンドウには、枠をつけることも可能。
Curses::Window#box(vert, hor)
ウィンドウの矩形領域の一番外側を枠で囲う。
このとき、垂直方向には文字vert
、水平方向には文字hor
が使われる。
ウィンドウに枠をつけるときに気をつけたいのが、この枠は矩形領域の外側につけられるわけではなく、矩形領域の内側につけられるということ。
このため、枠を作る文字の下にすでに文字があると上書きされてしまうし、逆に、枠のある位置に文字を上書きすると、枠を作る文字が消えてしまうことになる。
ウィンドウの変更の画面への反映
ここまでで、ウィンドウに対して文字の追加や削除を行う方法を説明した。
けど、これらの変更は、それだけでは画面には反映されない。
そこで、次はウィンドウに対する変更を画面に反映する方法について説明する。
ウィンドウの変更を仮想画面(と画面)へ反映するには、以下のメソッドを使う:
Curses::Window#noutrefresh()
ウィンドウの変更を仮想画面へ反映する。
(画面へは反映されない)
Curses::Window#refresh()
ウィンドウの変更を仮想画面と画面へ反映する。
Curses::Window#noutrefresh
メソッドを使った場合、仮想画面の内容を画面に反映する必要がある。
その場合、次のモジュール関数を使う:
Curses.#doupdate()
仮想画面の内容を画面へ反映する。
ウィンドウが一つしかない場合、refresh
を使うのとnoutrefresh
+ doupdate
を使うのは同じだけど、ウィンドウが複数あった場合、効率が変わってくる。
一般に、デバイスへのアクセスはメモリへのアクセスより遅いため、頻繁にデバイスへアクセスするのは効率が悪くなる。
そのため、複数のウィンドウを使う場合、各ウィンドウに対してnoutrefresh
をしたあとで最後に一度だけdoupdate
をする方が、効率がよくなる。
なお、複数のウィンドウがある場合、Cursesでは各ウィンドウがどの順番に重なっているのかは管理していない。
各ウィンドウの変更内容は、noutrefresh
(もしくはrefresh
)された順に仮想画面へ上書きされていく。
そのため、見かけ上は、あとでnoutrefresh
されたウィンドウの方が上に重なっているように見える。
ここまでの内容の確認と、refresh
/ noutrefresh
+ doupdate
の違いを見るために、次のコードを見てみる:
#==================== # multi_window.rb #-------------------- # refresh / noutrefresh + doupdateの違いを確認する #==================== require 'curses' class Curses::Window # タッチ更新 def touch self.move(self.begy, self.begx) end end Curses.init_screen Curses.cbreak screen = Curses.stdscr message = "Hello" windows = Array.new 10.times do |i| win = Curses::Window.new(3, 10, i+1, i+1) win.setpos(1, (10 - message.size) / 2) win.addstr(message) win.box('|', '-') windows.push win end # refresh screen.setpos(0, 0) screen.addstr("refresh") screen.refresh Curses.getch windows.each do |win| win.refresh sleep 0.1 end Curses.getch # noutrefresh + doupdate screen.clear screen.setpos(0, 0) screen.addstr("noutrefresh + doupdate") screen.refresh Curses.getch windows.each do |win| win.touch win.noutrefresh sleep 0.1 end Curses.doupdate Curses.getch windows.map(&:close) Curses.close_screen
このコードを実行すると、次のような結果になる:
- 画面左上に"refresh"と表示されて、入力待ちになる。
- 何かキーが押されると、少しずつ位置のずれたウィンドウが0.1秒間隔で表示されていき、最後に入力待ちになる。
- 何かキーが押されると、画面がクリアされる。
- 画面左上に"noutrefresh + doupdate"と表示され、入力待ちになる。
- 何かキーが押されると、しばらく経った後に、少しずつ位置のずれたウィンドウがすべて表示され、入力待ちになる。
- 何かキーが押されると、プログラムが終了する。
この結果から、次のことが確認できると思う:
refresh
を行うと、そのたびに画面に変更が反映される。noutrefresh
しただけでは、画面に変更は反映されない。doupdate
したとき、それまでの変更がすべて画面に反映される。- あとから
refresh
もしくはnoutrefresh
されたウィンドウが上に重なっているように見える。
なお、コードの最初の方でオープンクラスを使ってtouch
というメソッドを追加しているけど、これについてはまた後で。
今日はここまで!