tcellでTUIアプリを作成する

TUIとは、テキストユーザインタフェース(Text User Interface)の略で、文字を使ってコンピューターを操作するインタフェースです。

マウスやタッチパネルを使わず、キーボードからコマンドを入力することで、様々な操作を行います。



Go言語のtcellパッケージを用いて、TUIについて見ていくことにします。

gdamore/tcell: Tcell is an alternate terminal package, similar in some ways to termbox, but better in others.


余談ですが、tcellパッケージベースで開発されたアプリにMicroやfzf(fuzzy finder)があります。

ターミナル上でコードを書くためのMicroをインストールする

Microでシェルモードを使ってみる(fzf関連の記事)




tcellパッケージを使用できるようにします。

今回は例として、~/workspace/tcell_sample/ディレクトリ以下にファイルを作成することにします。


話を進める前に、最新版のGoをインストールするを参考にして、最新版のGoを使用できるようにしておきましょう。


ターミナルを開き、下記のコマンドを実行します。

$ cd ~
$ mkdir workspace
$ cd workspace
$ mkdir tcell_sample
$ cd tcell_sample
$ go mod init tcell_sample
$ go get github.com/gdamore/tcell/v2

$ micro main.go

でエディタを開き、下記のコードを作成します。


~/workspace/tcell_sample/main.go

package main

import (
	"github.com/gdamore/tcell/v2"
)

func main() {
	screen, err := tcell.NewScreen()
	if err != nil {
		panic(err)
	}
	if err := screen.Init(); err != nil {
		panic(err)
	}
	defer screen.Fini()

	
	for {
		screen.SetContent(0, 0, 'H', nil, tcell.StyleDefault)
		screen.SetContent(1, 0, 'i', nil, tcell.StyleDefault)
		screen.SetContent(2, 0, '!', nil, tcell.StyleDefault)

		screen.Show()
	}
}

エディタを閉じ、ターミナル上で、

$ go run main.go

を実行して、



画面が切り替わることを確認しましょう。




コードの詳細を見ていきます。


screen, err := tcell.NewScreen()
if err != nil {
	panic(err)
}
if err := screen.Init(); err != nil {
	panic(err)
}
defer screen.Fini()

上記のコードで、ターミナルからTUIに切り替わる手続きを行います。


最後の行の

defer screen.Fini()

はmain関数(エンドポイント)内の処理が最後まで行われた後の実行の予約になり、スクリーンの終了を予約しておきます。


次の行からの for で囲まれた箇所はイベントループになりまして、tcellでTUIアプリを作る時は設ける必要があります。

イベントループ - Wikipedia


今回のコードでは、イベントループ内で Hi! という文字列を同じ場所に何度も出力させるようにしています。

func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, st Style)

https://github.com/gdamore/tcell/blob/main/screen.go#L404




今回のコードでは、main関数を終了させる術がありません。

イベントループを抜ける為に

for {
	screen.SetContent(0, 0, 'H', nil, tcell.StyleDefault)
	screen.SetContent(1, 0, 'i', nil, tcell.StyleDefault)
	screen.SetContent(2, 0, '!', nil, tcell.StyleDefault)

	screen.Show()
}

for 内のコードを

for {
	ev := screen.PollEvent() // イベントを取得する
	switch ev := ev.(type) {
	case *tcell.EventKey:
		switch ev.Key() { // 何のキーが押されたか?を調べる
		case tcell.KeyEscape: // ESCキーが押されたら終了する
			return
		}
	default:
		screen.SetContent(0, 0, 'H', nil, tcell.StyleDefault)
		screen.SetContent(1, 0, 'i', nil, tcell.StyleDefault)
		screen.SetContent(2, 0, '!', nil, tcell.StyleDefault)
	}

	screen.Show()
}

に書き換えます。


変更した内容は、何らかのイベントを取得し、イベントの内、キーを押すイベント(EventKey)とそれ以外のイベントに分けます。

受け取ったイベントがEventKeyであった場合は、何のキーを押されたか?を調べ、ESCキーであれば、イベントループを抜けて、main関数を終了させるという処理になります。


追記

サンプルコード集

https://github.com/gdamore/tcell/tree/main/_demos

同じカテゴリーの記事
マインクラフト用ビジュアルエディタを開発しています。
詳しくはinunosinsi/mcws_blockly - githubをご覧ください。