tcell
GitHub概要
gdamore/tcell
Tcell is an alternate terminal package, similar in some ways to termbox, but better in others.
スター4,879
ウォッチ72
フォーク329
作成日:2015年9月27日
言語:Go
ライセンス:Apache License 2.0
トピックス
なし
スター履歴
データ取得日時: 2025/7/25 11:09
tcell
tcellは、Go言語向けの低レベルターミナル操作ライブラリです。クロスプラットフォームで動作し、モダンなターミナル機能をサポートします。tviewやcviewなど、多くの高レベルTUIライブラリの基盤として使用されています。
特徴
クロスプラットフォーム
- OSサポート: Windows、macOS、Linux、多くのUnix系システム
- ターミナル互換: xterm、VT100、Windows Consoleなど
- SSHサポート: SSHセッション経由での動作
- 組み込みテーマ: 256色およびTrue Colorサポート
モダンな機能
- Unicodeサポート: 完全なUnicode対応(絵文字、結合文字含む)
- マウスサポート: クリック、ドラッグ、ホイールイベント
- リサイズイベント: ターミナルサイズ変更の検出
- パフォーマンス: 効率的なセルバッファリング
低レベルAPI
- 直接セル操作: 座標指定での文字描画
- 属性制御: 色、太字、下線、点滅など
- イベントシステム: キーボード、マウス、リサイズイベント
- バッファリング: ダブルバッファリングによるちらつき防止
拡張性
- カスタムターミナル: 新しいターミナルタイプの追加
- シミュレーション: テスト用の仮想スクリーン
- テーマシステム: カスタムカラースキーム
- 国際化: ロケール対応
基本的な使用方法
インストール
go get github.com/gdamore/tcell/v2
Hello World
package main
import (
"fmt"
"os"
"github.com/gdamore/tcell/v2"
)
func main() {
// スクリーンの初期化
s, err := tcell.NewScreen()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
if err := s.Init(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
defer s.Fini()
// デフォルトスタイル
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
s.SetStyle(defStyle)
// 画面クリア
s.Clear()
// "Hello, World!"を表示
text := "Hello, World!"
x, y := 10, 5
style := tcell.StyleDefault.Foreground(tcell.ColorCyan).Background(tcell.ColorBlue)
for i, r := range text {
s.SetContent(x+i, y, r, nil, style)
}
// 描画
s.Show()
// イベントループ
for {
ev := s.PollEvent()
switch ev := ev.(type) {
case *tcell.EventKey:
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
return
}
case *tcell.EventResize:
s.Sync()
}
}
}
インタラクティブなアプリケーション
package main
import (
"fmt"
"os"
"time"
"github.com/gdamore/tcell/v2"
)
type App struct {
screen tcell.Screen
px, py int
mx, my int
style tcell.Style
}
func NewApp() (*App, error) {
s, err := tcell.NewScreen()
if err != nil {
return nil, err
}
if err := s.Init(); err != nil {
return nil, err
}
s.EnableMouse()
s.EnablePaste()
s.Clear()
return &App{
screen: s,
px: 10,
py: 10,
style: tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorWhite),
}, nil
}
func (a *App) Run() {
defer a.screen.Fini()
// ステータスバーを描画
go a.drawStatus()
// メインループ
for {
a.draw()
a.screen.Show()
ev := a.screen.PollEvent()
if !a.handleEvent(ev) {
return
}
}
}
func (a *App) draw() {
w, h := a.screen.Size()
// 枠を描画
for x := 0; x < w; x++ {
a.screen.SetContent(x, 0, '─', nil, a.style)
a.screen.SetContent(x, h-2, '─', nil, a.style)
}
for y := 1; y < h-2; y++ {
a.screen.SetContent(0, y, '│', nil, a.style)
a.screen.SetContent(w-1, y, '│', nil, a.style)
}
// コーナー
a.screen.SetContent(0, 0, '┌', nil, a.style)
a.screen.SetContent(w-1, 0, '┐', nil, a.style)
a.screen.SetContent(0, h-2, '└', nil, a.style)
a.screen.SetContent(w-1, h-2, '┘', nil, a.style)
// プレイヤーを描画
playerStyle := tcell.StyleDefault.Foreground(tcell.ColorYellow)
a.screen.SetContent(a.px, a.py, '@', nil, playerStyle)
// マウス位置を描画
if a.mx > 0 && a.my > 0 {
mouseStyle := tcell.StyleDefault.Foreground(tcell.ColorRed)
a.screen.SetContent(a.mx, a.my, 'X', nil, mouseStyle)
}
}
func (a *App) drawStatus() {
for {
w, h := a.screen.Size()
statusStyle := tcell.StyleDefault.Background(tcell.ColorBlue).Foreground(tcell.ColorWhite)
// ステータスバーをクリア
for x := 0; x < w; x++ {
a.screen.SetContent(x, h-1, ' ', nil, statusStyle)
}
// 情報を表示
status := fmt.Sprintf(" Pos: (%d,%d) | Size: %dx%d | Time: %s | ESC: Exit ",
a.px, a.py, w, h, time.Now().Format("15:04:05"))
for i, r := range status {
if i < w {
a.screen.SetContent(i, h-1, r, nil, statusStyle)
}
}
a.screen.Show()
time.Sleep(1 * time.Second)
}
}
func (a *App) handleEvent(ev tcell.Event) bool {
switch ev := ev.(type) {
case *tcell.EventKey:
return a.handleKey(ev)
case *tcell.EventMouse:
return a.handleMouse(ev)
case *tcell.EventResize:
a.screen.Sync()
return true
}
return true
}
func (a *App) handleKey(ev *tcell.EventKey) bool {
w, h := a.screen.Size()
switch ev.Key() {
case tcell.KeyEscape, tcell.KeyCtrlC:
return false
case tcell.KeyUp:
if a.py > 1 {
a.py--
}
case tcell.KeyDown:
if a.py < h-3 {
a.py++
}
case tcell.KeyLeft:
if a.px > 1 {
a.px--
}
case tcell.KeyRight:
if a.px < w-2 {
a.px++
}
}
switch ev.Rune() {
case 'h':
if a.px > 1 {
a.px--
}
case 'j':
if a.py < h-3 {
a.py++
}
case 'k':
if a.py > 1 {
a.py--
}
case 'l':
if a.px < w-2 {
a.px++
}
}
return true
}
func (a *App) handleMouse(ev *tcell.EventMouse) bool {
x, y := ev.Position()
a.mx, a.my = x, y
switch ev.Buttons() {
case tcell.Button1:
// 左クリックで移動
w, h := a.screen.Size()
if x > 0 && x < w-1 && y > 0 && y < h-2 {
a.px, a.py = x, y
}
}
return true
}
func main() {
app, err := NewApp()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
app.Run()
}
Unicodeと絵文字のサポート
package main
import (
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
)
func drawText(s tcell.Screen, x, y int, style tcell.Style, text string) {
for _, r := range text {
s.SetContent(x, y, r, nil, style)
x += runewidth.RuneWidth(r)
}
}
func main() {
s, _ := tcell.NewScreen()
s.Init()
defer s.Fini()
s.Clear()
// 絵文字と日本語を表示
style := tcell.StyleDefault
drawText(s, 5, 2, style, "🌟 Star ⭐")
drawText(s, 5, 3, style, "🚀 Rocket")
drawText(s, 5, 4, style, "🌈 Rainbow")
drawText(s, 5, 6, style, "こんにちは世界🌏")
drawText(s, 5, 7, style, "テキストベースUI💻")
s.Show()
// キー待ち
for {
ev := s.PollEvent()
if ev, ok := ev.(*tcell.EventKey); ok {
if ev.Key() == tcell.KeyEscape {
return
}
}
}
}
カスタムウィジェットの作成
package main
import (
"github.com/gdamore/tcell/v2"
)
// ボタンウィジェット
type Button struct {
x, y int
width int
text string
style tcell.Style
hover bool
click func()
}
func NewButton(x, y int, text string) *Button {
return &Button{
x: x,
y: y,
width: len(text) + 4,
text: text,
style: tcell.StyleDefault.Background(tcell.ColorBlue).Foreground(tcell.ColorWhite),
}
}
func (b *Button) Draw(s tcell.Screen) {
style := b.style
if b.hover {
style = style.Background(tcell.ColorLightBlue)
}
// ボタンの背景
for i := 0; i < b.width; i++ {
s.SetContent(b.x+i, b.y, ' ', nil, style)
}
// テキスト
textX := b.x + (b.width-len(b.text))/2
for i, r := range b.text {
s.SetContent(textX+i, b.y, r, nil, style)
}
}
func (b *Button) HandleMouse(x, y int, buttons tcell.ButtonMask) {
if x >= b.x && x < b.x+b.width && y == b.y {
b.hover = true
if buttons&tcell.Button1 != 0 && b.click != nil {
b.click()
}
} else {
b.hover = false
}
}
// プログレスバーウィジェット
type ProgressBar struct {
x, y int
width int
progress float64
style tcell.Style
}
func NewProgressBar(x, y, width int) *ProgressBar {
return &ProgressBar{
x: x,
y: y,
width: width,
style: tcell.StyleDefault,
}
}
func (p *ProgressBar) SetProgress(progress float64) {
if progress < 0 {
progress = 0
} else if progress > 1 {
progress = 1
}
p.progress = progress
}
func (p *ProgressBar) Draw(s tcell.Screen) {
// 枠
s.SetContent(p.x, p.y, '[', nil, p.style)
s.SetContent(p.x+p.width-1, p.y, ']', nil, p.style)
// バー
filled := int(float64(p.width-2) * p.progress)
for i := 1; i < p.width-1; i++ {
if i <= filled {
s.SetContent(p.x+i, p.y, '█', nil, p.style.Foreground(tcell.ColorGreen))
} else {
s.SetContent(p.x+i, p.y, '░', nil, p.style.Foreground(tcell.ColorDarkGray))
}
}
}
高度な機能
True Colorサポート
// RGB色の使用
style := tcell.StyleDefault.
Background(tcell.NewRGBColor(32, 32, 32)).
Foreground(tcell.NewRGBColor(255, 128, 0))
// HSL色の使用
hslColor := tcell.NewHSLColor(120, 100, 50) // 緑色
シミュレーションスクリーン
// テスト用の仮想スクリーン
simScreen := tcell.NewSimulationScreen("UTF-8")
simScreen.Init()
simScreen.SetSize(80, 24)
// イベントのインジェクション
simScreen.InjectKey(tcell.KeyEnter, ' ', tcell.ModNone)
simScreen.InjectMouse(10, 5, tcell.Button1, tcell.ModNone)
カスタムターミナルの定義
import "github.com/gdamore/tcell/v2/terminfo"
// カスタムターミナル情報の作成
ti := &terminfo.Terminfo{
Name: "myterm",
Columns: 80,
Lines: 24,
Colors: 256,
// その他の設定...
}
// 登録
terminfo.AddTerminfo(ti)
エコシステム
tcellを基盤とするライブラリ
- tview: リッチなウィジェットライブラリ
- cview: tviewのフォーク(並行性重視)
- tcell-term: ターミナルエミュレータ
- gowid: ウィジェットライブラリ
採用例
- micro: モダンなテキストエディタ
- aerc: メールクライアント
- fzf: ファジーファインダー(オプション)
利点
- クロスプラットフォーム: 幅広いOSとターミナルサポート
- モダン: Unicode、True Color、マウスサポート
- パフォーマンス: 効率的なレンダリング
- 安定性: 成熟したコードベース
- 柔軟性: 低レベルAPIで完全な制御
制約事項
- 低レベル: 高レベルウィジェットは自作が必要
- 学習曲線: 直接セル操作の理解が必要
- ボイラープレート: 基本的な機能も自分で実装
- ドキュメント: APIドキュメント中心
他のライブラリとの比較
項目 | tcell | termbox-go | ncurses |
---|---|---|---|
レベル | 低 | 低 | 低 |
クロスプラットフォーム | 優秀 | 良好 | Unix系のみ |
モダン機能 | 完全 | 基本的 | 部分的 |
パフォーマンス | 優秀 | 優秀 | 良好 |
Goネイティブ | ◯ | ◯ | × |
まとめ
tcellは、Go言語でクロスプラットフォームなターミナルアプリケーションを構築するための強力な低レベルライブラリです。モダンなターミナル機能を完全にサポートし、多くの人気TUIライブラリの基盤となっています。高レベルウィジェットが必要な場合はtviewなどを使用することをお勧めしますが、完全な制御が必要な場合やカスタムTUIフレームワークを構築する場合には、tcellが最適な選択肢です。