Library

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

nkUp nkDown nkLeft nkRight nkEscape Esc nkEnter Enter nkBackspace Backspace nkChar 通常文字 nkNone 入力なし

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アプリ。

Dependencies

nim >= 2.0.0

Links