Nimact
TUI Framework for Nim
About
Nimactは、Nim言語のための モダンでシンプルなTUIフレームワーク だにゃん。 コンポーネントベースで宣言的にUIを構築でき、自動レイアウト・差分描画・TrueColor・Unicodeに対応しているにゃ! MITライセンス。バージョン 1.2.0。 GitHub で公開中にゃ。
Installation
nimble install nimact
.nimbleのdependenciesに requires "nimact" を追加してにゃ。
Quick Start
import std/asyncdispatch import nimact var count = 0 let app = newApp() proc build(): Widget = vbox( header("My App", fg = colWhite, bg = colBlue, bold = true), center(40, 10, label("Counter: " & $count, fg = colGreen, bold = true), progress(count.float, max = 20.0, fg = colBlue) ), footer("SPACE: +1 | Q: Quit", fg = colTextMuted, bg = colBgDark) ) app.onKey(' ', proc() = inc count) app.onKey('q', proc() = app.quit()) waitFor app.run(build)
Core Concepts
App
アプリケーションの状態を管理。キーイベントの登録、メインループの実行、終了処理を行う。
Widget
UIコンポーネントを表現するツリー構造。label, vbox, hbox, center, box, header, footer, progress, separator, spacer の10種類。
EventBus
キー入力をハンドラに配信するpub-subシステム。文字キー・矢印キー・Escape・Enterの各イベントに複数ハンドラを登録可能。
Buffer + 差分描画
2Dセル配列で画面を表現。前フレームとの差分のみを描画して60FPSを実現。
Components API
label
label(text, fg, bg, bold, placeholder, placeholderFg, placeholderBg, showPlaceholder): Widget テキストを表示。Unicode対応。プレースホルダー機能あり。
Layouts
vbox(children...): Widget 子を縦に積む。gap(行数)とstyle(背景色)を指定可能。
vbox(gap, children...): Widget 隙間を指定して縦に積む。
vbox(style, children...): Widget 背景色を指定して縦に積む。
vbox(gap, style, children...): Widget 隙間と背景色を指定。
hbox(children...): Widget 子を横に並べる。gap(列数)とstyleを指定可能。
center(w, h, children...): Widget 指定サイズの領域の中央に子を配置。
Containers
box(w, h, style=style(), borderType=bsRounded, children...): Widget 罫線付きのパネル。borderType: bsSingle, bsDouble, bsRounded, bsBold。
Special Bars
header(text, fg, bg, bold): Widget 画面上部に固定される全幅バー。
footer(text, fg, bg, bold): Widget 画面下部に固定される全幅バー。
Decorations
progress(value, max=1.0, fg, bg): Widget プログレスバー。█と░で進捗を表示。
separator(fg, bg): Widget 水平区切り線(─)。
spacer(height=1): Widget 空行スペース。
App API
newApp(): App アプリケーションを作成
app.run(build): Future[void] メインループを開始。buildは毎フレーム呼ばれる。
app.quit() アプリケーションを終了
app.onKey(ch, handler) 文字キーのハンドラを登録 (例: 'q', ' ')
app.onKey(keyKind, handler) 特殊キーのハンドラを登録 (nkUp, nkDown, nkLeft, nkRight, nkEscape, nkEnter, nkBackspace)
app.onAnyChar(handler) 全ての文字キー入力を一括で処理
Color Palette
colBlue
#61afef
colPurple
#c678dd
colGreen
#98c379
colYellow
#e5c07b
colRed
#e06c75
colCyan
#56b6c2
colText
#dcdfe4
colTextMuted
#5c6370
colWhite
#ffffff
colBgDark
#1e222a
colBgCard
#282c34
colBgFocus
#323842
カスタムカラー: rgb(r, g, b): Color または defaultColor() でターミナルデフォルト色。
Style Object
style(fg, bg, bold, dim, italic, underline, reverse): Style 全てのパラメータはオプショナル。デフォルトは装飾なし+ターミナルデフォルト色。
Low-level Drawing
buf.drawString(x, y, str, style=style()) バッファに文字列を描画
buf.drawBox(x, y, w, h, style=style(), borderType=bsRounded) 罫線付き矩形を描画
Key Kind Reference
Examples
hello.nim — Hello World
import std/asyncdispatch import nimact let app = newApp() proc build(): Widget = vbox( header("Hello App", fg = colWhite, bg = colBlue, bold = true), center(40, 5, label("Hello, Nimact!", fg = colGreen, bold = true) ), footer("Q: Quit", fg = colTextMuted, bg = colBgDark) ) app.onKey('q', proc() = app.quit()) app.onKey('Q', proc() = app.quit()) waitFor app.run(build)
最小構成。header/center/footer の基本レイアウト。Qで終了。
counter.nim — カウンター
import std/asyncdispatch import nimact var count = 0 let app = newApp() proc build(): Widget = vbox( header("nimact examples v0.1.0", fg = colWhite, bg = colBlue, bold = true), center(40, 10, label(" [ カウンター ] ", fg = colPurple, bold = true), label(""), label("SPACE をおして加算"), label(""), label("現在のカウント: " & $count, fg = colGreen, bold = true), label(""), progress(count.float, max = 20.0, fg = colBlue) ), footer(" Q: 終了 | SPACE: 加算 ", fg = colTextMuted, bg = colBgDark) ) app.onKey(' ', proc() = inc count) app.onKey('q', proc() = app.quit()) app.onKey('Q', proc() = app.quit()) waitFor app.run(build)
外部変数countをbuild()内で参照 → SPACEで加算が自動反映。progressバー付き。
dashboard.nim — ダッシュボード
import std/asyncdispatch import nimact var reqCount = 0, cpuUsage = 35.0, memUsage = 62.0 let app = newApp() proc build(): Widget = vbox( header(" Dashboard Like App ", fg = colWhite, bg = colBlue, bold = true), hbox(2, vbox(1, label(" [ リクエスト ] ", fg = colPurple, bold = true), label("トータル: " & $reqCount), label(" [ CPU ] ", fg = colPurple, bold = true), progress(cpuUsage, max = 100.0, fg = colGreen), label(" [ Memory ] ", fg = colPurple, bold = true), progress(memUsage, max = 100.0, fg = colYellow), ), vbox(3, label(" [ ステータス ] ", fg = colPurple, bold = true), label(" サーバー: 実行中", fg = colGreen), label(" バージョン: 0.1.0", fg = colYellow), ), ), footer(" SPACE: +request | R: Reset | Q: Quit ", fg = colTextMuted, bg = colBgDark) ) app.onKey(' ', proc() = inc reqCount; cpuUsage += 2.5; memUsage += 1.0) app.onKey('r', proc() = reqCount=0; cpuUsage=35.0; memUsage=62.0) app.onKey('q', proc() = app.quit()) waitFor app.run(build)
hboxで左右カラム + 複数vboxネスト。色分けされたメトリクス表示。
todo.nim — TODOリスト
import std/asyncdispatch import nimact type TodoItem = object text: string done: bool var todos: seq[TodoItem] = @[ TodoItem(text: "Learn Nim basics", done: true), TodoItem(text: "Build a TUI app", done: true), TodoItem(text: "Publish to GitHub", done: false), ] var cursor = 0 let app = newApp() proc build(): Widget = var ws: seq[Widget] ws.add(label(" [ TODO リスト ] ", fg = colPurple, bold = true)) for i, item in todos: let marker = if i == cursor: "> " else: " " let prefix = if item.done: "[x]" else: "[ ]" ws.add(label(marker & prefix & " " & item.text, fg = if item.done: colTextMuted elif i==cursor: colGreen else: colText)) vbox( header(" Todo App ", fg = colWhite, bg = colBlue, bold = true), center(50, todos.len + 14, ws), footer(" ↑↓: Select | Enter: Add | Q: Quit ", fg = colTextMuted, bg = colBgDark) ) app.onKey(nkUp, proc() = cursor = (cursor-1+todos.len) mod todos.len) app.onKey(nkDown, proc() = cursor = (cursor+1) mod todos.len) app.onKey(nkEnter, proc() = todos.add(TodoItem(text: "new item", done: false))) app.onKey(nkEscape, proc() = app.quit()) app.onKey('q', proc() = app.quit()) waitFor app.run(build)
↑↓選択・Enter追加・完了トグル・削除・プログレスバー完備の本格的なTODOアプリ。