termbox-go
GitHub概要
nsf/termbox-go
Pure Go termbox implementation
スター4,734
ウォッチ92
フォーク375
作成日:2012年1月12日
言語:Go
ライセンス:MIT License
トピックス
なし
スター履歴
データ取得日時: 2025/7/25 11:09
termbox-go
termbox-goは、シンプルで軽量なターミナルUIライブラリです。C言語のtermboxライブラリのPure Go実装で、ミニマリストな設計と最小限の依存関係により、素早くターミナルアプリケーションを開発できます。
特徴
シンプルなAPI
- 最小限の機能: 必要不可欠な機能のみ提供
- 簡潔な設計: 理解しやすいAPI
- 低レベル操作: セル単位の直接制御
- イベント駆動: キーボードとマウスイベント
パフォーマンス
- 軽量: 最小限のメモリ使用
- 高速: 効率的なレンダリング
- Pure Go: CGO不要
- ゼロ依存: 外部ライブラリ不要
互換性
- クロスプラットフォーム: Windows、macOS、Linux
- 256色サポート: モダンターミナル対応
- UTF-8サポート: Unicode文字の表示
- マウスサポート: クリックイベント対応
バッファリング
- ダブルバッファ: ちらつき防止
- セルバッファ: 効率的な画面更新
- 差分更新: 必要な部分のみ再描画
- スマートレンダリング: 最適化された描画
基本的な使用方法
インストール
go get github.com/nsf/termbox-go
Hello World
package main
import (
"github.com/nsf/termbox-go"
)
func main() {
err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()
// 画面クリア
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
// "Hello, World!"を表示
text := "Hello, World!"
for i, ch := range text {
termbox.SetCell(i+5, 5, ch, termbox.ColorYellow, termbox.ColorBlue)
}
// 描画
termbox.Flush()
// キー待ち
termbox.PollEvent()
}
イベントハンドリング
package main
import (
"fmt"
tb "github.com/nsf/termbox-go"
)
func main() {
err := tb.Init()
if err != nil {
panic(err)
}
defer tb.Close()
// マウスサポートを有効化
tb.SetInputMode(tb.InputEsc | tb.InputMouse)
drawAll()
// イベントループ
for {
switch ev := tb.PollEvent(); ev.Type {
case tb.EventKey:
if ev.Key == tb.KeyEsc || ev.Key == tb.KeyCtrlC {
return
}
handleKey(ev)
case tb.EventMouse:
handleMouse(ev)
case tb.EventResize:
drawAll()
case tb.EventError:
panic(ev.Err)
}
tb.Flush()
}
}
func drawAll() {
w, h := tb.Size()
tb.Clear(tb.ColorDefault, tb.ColorDefault)
// ヘッダー
drawBox(0, 0, w, 3, "Termbox-go Demo")
// ステータスバー
status := fmt.Sprintf(" Size: %dx%d | Press ESC to quit ", w, h)
drawText(0, h-1, w, status, tb.ColorWhite, tb.ColorBlue)
// 中央メッセージ
msg := "Press any key or click the mouse"
x := (w - len(msg)) / 2
y := h / 2
drawText(x, y, len(msg), msg, tb.ColorYellow, tb.ColorDefault)
}
func drawBox(x, y, w, h int, title string) {
// 上線
tb.SetCell(x, y, '┌', tb.ColorWhite, tb.ColorDefault)
for i := 1; i < w-1; i++ {
tb.SetCell(x+i, y, '─', tb.ColorWhite, tb.ColorDefault)
}
tb.SetCell(x+w-1, y, '┐', tb.ColorWhite, tb.ColorDefault)
// 側線
for i := 1; i < h-1; i++ {
tb.SetCell(x, y+i, '│', tb.ColorWhite, tb.ColorDefault)
tb.SetCell(x+w-1, y+i, '│', tb.ColorWhite, tb.ColorDefault)
}
// 下線
tb.SetCell(x, y+h-1, '└', tb.ColorWhite, tb.ColorDefault)
for i := 1; i < w-1; i++ {
tb.SetCell(x+i, y+h-1, '─', tb.ColorWhite, tb.ColorDefault)
}
tb.SetCell(x+w-1, y+h-1, '┘', tb.ColorWhite, tb.ColorDefault)
// タイトル
if title != "" {
titleX := x + (w-len(title))/2
drawText(titleX, y, len(title), title, tb.ColorCyan, tb.ColorDefault)
}
}
func drawText(x, y, w int, text string, fg, bg tb.Attribute) {
for i, ch := range text {
if i < w {
tb.SetCell(x+i, y, ch, fg, bg)
}
}
}
func handleKey(ev tb.Event) {
w, h := tb.Size()
info := fmt.Sprintf("Key: %v, Char: %c", ev.Key, ev.Ch)
x := (w - len(info)) / 2
y := h/2 + 2
// クリア
for i := 0; i < w; i++ {
tb.SetCell(i, y, ' ', tb.ColorDefault, tb.ColorDefault)
}
drawText(x, y, len(info), info, tb.ColorGreen, tb.ColorDefault)
}
func handleMouse(ev tb.Event) {
w, h := tb.Size()
info := fmt.Sprintf("Mouse: X=%d, Y=%d, Button=%v", ev.MouseX, ev.MouseY, ev.Key)
x := (w - len(info)) / 2
y := h/2 + 2
// クリア
for i := 0; i < w; i++ {
tb.SetCell(i, y, ' ', tb.ColorDefault, tb.ColorDefault)
}
drawText(x, y, len(info), info, tb.ColorMagenta, tb.ColorDefault)
// マウス位置にマーカー
tb.SetCell(ev.MouseX, ev.MouseY, 'X', tb.ColorRed, tb.ColorDefault)
}
ゲームの作成
package main
import (
"math/rand"
"time"
tb "github.com/nsf/termbox-go"
)
type Game struct {
px, py int // プレイヤー位置
ex, ey int // 敵位置
score int
gameOver bool
}
func NewGame() *Game {
w, h := tb.Size()
return &Game{
px: w / 2,
py: h / 2,
ex: rand.Intn(w),
ey: rand.Intn(h),
}
}
func (g *Game) Update(ev tb.Event) {
if g.gameOver {
return
}
w, h := tb.Size()
// プレイヤー移動
switch ev.Ch {
case 'w', 'W':
if g.py > 0 {
g.py--
}
case 's', 'S':
if g.py < h-1 {
g.py++
}
case 'a', 'A':
if g.px > 0 {
g.px--
}
case 'd', 'D':
if g.px < w-1 {
g.px++
}
}
// 敵を捕まえたかチェック
if g.px == g.ex && g.py == g.ey {
g.score++
g.ex = rand.Intn(w)
g.ey = rand.Intn(h)
}
// 敵のランダム移動
if rand.Float32() < 0.1 {
g.ex += rand.Intn(3) - 1
g.ey += rand.Intn(3) - 1
if g.ex < 0 {
g.ex = 0
}
if g.ex >= w {
g.ex = w - 1
}
if g.ey < 0 {
g.ey = 0
}
if g.ey >= h {
g.ey = h - 1
}
}
}
func (g *Game) Draw() {
tb.Clear(tb.ColorDefault, tb.ColorDefault)
// プレイヤー
tb.SetCell(g.px, g.py, '@', tb.ColorYellow, tb.ColorDefault)
// 敵
tb.SetCell(g.ex, g.ey, '*', tb.ColorRed, tb.ColorDefault)
// スコア
scoreText := fmt.Sprintf("Score: %d | WASD to move | ESC to quit", g.score)
for i, ch := range scoreText {
tb.SetCell(i, 0, ch, tb.ColorWhite, tb.ColorDefault)
}
tb.Flush()
}
func main() {
err := tb.Init()
if err != nil {
panic(err)
}
defer tb.Close()
rand.Seed(time.Now().UnixNano())
game := NewGame()
// タイマーイベント用チャネル
eventQueue := make(chan tb.Event)
go func() {
for {
eventQueue <- tb.PollEvent()
}
}()
// ゲームループ
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
game.Draw()
for {
select {
case ev := <-eventQueue:
if ev.Type == tb.EventKey && (ev.Key == tb.KeyEsc || ev.Key == tb.KeyCtrlC) {
return
}
game.Update(ev)
game.Draw()
case <-ticker.C:
game.Update(tb.Event{})
game.Draw()
}
}
}
カスタムウィジェット
package main
import tb "github.com/nsf/termbox-go"
// ボタンウィジェット
type Button struct {
x, y int
width int
text string
active bool
}
func NewButton(x, y int, text string) *Button {
return &Button{
x: x,
y: y,
width: len(text) + 4,
text: text,
}
}
func (b *Button) Draw() {
fg := tb.ColorWhite
bg := tb.ColorBlue
if b.active {
bg = tb.ColorRed
}
// ボタン背景
for i := 0; i < b.width; i++ {
tb.SetCell(b.x+i, b.y, ' ', fg, bg)
}
// テキスト
textX := b.x + (b.width-len(b.text))/2
for i, ch := range b.text {
tb.SetCell(textX+i, b.y, ch, fg, bg)
}
}
func (b *Button) HandleClick(x, y int) bool {
if y == b.y && x >= b.x && x < b.x+b.width {
b.active = !b.active
return true
}
return false
}
// プログレスバーウィジェット
type ProgressBar struct {
x, y int
width int
progress float64
}
func NewProgressBar(x, y, width int) *ProgressBar {
return &ProgressBar{
x: x,
y: y,
width: width,
}
}
func (p *ProgressBar) SetProgress(progress float64) {
if progress < 0 {
progress = 0
} else if progress > 1 {
progress = 1
}
p.progress = progress
}
func (p *ProgressBar) Draw() {
// 枠
tb.SetCell(p.x, p.y, '[', tb.ColorWhite, tb.ColorDefault)
tb.SetCell(p.x+p.width-1, p.y, ']', tb.ColorWhite, tb.ColorDefault)
// バー
filled := int(float64(p.width-2) * p.progress)
for i := 1; i < p.width-1; i++ {
if i <= filled {
tb.SetCell(p.x+i, p.y, '█', tb.ColorGreen, tb.ColorDefault)
} else {
tb.SetCell(p.x+i, p.y, '░', tb.ColorGray, tb.ColorDefault)
}
}
// パーセンテージ
percent := fmt.Sprintf(" %d%% ", int(p.progress*100))
percentX := p.x + (p.width-len(percent))/2
for i, ch := range percent {
fg := tb.ColorWhite
if percentX+i-p.x-1 <= filled {
fg = tb.ColorBlack
}
tb.SetCell(percentX+i, p.y, ch, fg, tb.ColorDefault)
}
}
高度な機能
256色モード
// 256色モードを有効化
err := tb.SetOutputMode(tb.Output256)
if err != nil {
// フォールバック
tb.SetOutputMode(tb.OutputNormal)
}
// 256色を使用
tb.SetCell(x, y, '█', tb.Attribute(196), tb.ColorDefault) // 赤
バッファ操作
// バックバッファを直接操作
cells := tb.CellBuffer()
w, h := tb.Size()
// 高速な全画面塗りつぶし
for i := 0; i < w*h; i++ {
cells[i] = tb.Cell{
Ch: ' ',
Fg: tb.ColorDefault,
Bg: tb.ColorBlue,
}
}
カスタム入力モード
// Altキーを有効化
tb.SetInputMode(tb.InputEsc | tb.InputAlt)
// イベント処理
if ev.Type == tb.EventKey && ev.Mod == tb.ModAlt {
switch ev.Ch {
case 'f':
// Alt+F
}
}
エコシステム
termbox-goを使用したプロジェクト
- micro: モダンなテキストエディタ
- lf: ファイルマネージャー
- hecate: Hexエディタ
- goyo: ディストラクションフリーライティング
代替ライブラリ
- tcell: より機能豊富な代替
- termui: ダッシュボード向け
- gocui: ビューベースの代替
利点
- シンプル: 学習が容易
- 軽量: 最小限のリソース使用
- 高速: 効率的なレンダリング
- 独立: 外部依存なし
- 安定: 成熟したコードベース
制約事項
- 機能限定: 基本機能のみ
- メンテナンス: 開発が停滞
- ウィジェットなし: すべて自作が必要
- モダン機能: True Colorなど未サポート
他のライブラリとの比較
項目 | termbox-go | tcell | gocui |
---|---|---|---|
レベル | 最低 | 低 | 中 |
機能 | 基本的 | 豊富 | 中程度 |
パフォーマンス | 優秀 | 優秀 | 良好 |
依存関係 | なし | 少 | 少 |
メンテナンス | 停滞 | 活発 | 低 |
まとめ
termbox-goは、シンプルで軽量なターミナルUIライブラリを求める開発者に最適な選択肢です。最小限の機能とゼロ依存により、学習が容易で、パフォーマンスも優れています。現在はメンテナンスが停滞しているため、新規プロジェクトではtcellなどの代替を検討することをお勧めしますが、既存のプロジェクトやシンプルなツールには依然として有用なライブラリです。