VUIを使ってみた

## VUIとは

[VUI](https://github.com/waldson/vui)

Vim上でテキストベースのUIを構築するためのライブラリです。

## 確認環境

* Neovim v0.3.1

## インストール

dein.vim

```toml
[[plugins]]
repo = 'waldson/vui'
```

## 使い方

```vimscript
let screen = vui#screen#new()

function! screen.on_before_render(screen) abort
  let panel = vui#component#panel#new('@my-tasks', winwidth(0), winheight(0))
  let vcontainer = vui#component#vcontainer#new()

  for item in ['hoge', 'fuga', 'piyo']
    let text = vui#component#text#new(item)
    call vcontainer.add_child(text)
  endfor

  call panel.get_content_component().add_child(vcontainer)
  call a:screen.set_root_component(panel)
endfunction

call screen.show()
```

#### Screenを作成する

Screenは入力イベントの処理やコンポーネントの表示等を行います。

```vim
let screen = vui#screen#new()
```

#### Screenにコンポーネントを登録する

`on_before_render`を設定しておくと、描画前に実行されるので、この中でコンポーネントを登録します。

```vim
function! screen.on_before_render(screen) abort
  let panel = vui#component#panel#new('Title', winwidth(0), winheight(0))
  let vcontainer = vui#component#vcontainer#new()

  for item in ['hoge', 'fuga', 'piyo']
    let text = vui#component#text#new(item)
    call vcontainer.add_child(text)
  endfor

  call panel.get_content_component().add_child(vcontainer)
  call a:screen.set_root_component(panel)
endfunction
```


#### コンポーネントを描画する

```vimscript
call screen.show()
```

![vui-example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240412/3591922b-39da-6248-cd85-cf250128f894.png)

## コンポーネント

### panel

```vimscript
let panel = vui#component#panel#new('Title', width, height)
let container = panel.get_content_component()

call container.add_child(child1)
call container.add_child(child2)
```

タイトルを持つボックス
子コンポーネントを登録する際は、`get_content_component`でコンテナを取得する必要があります。

### vcontainer

```vimscript
let vcontainer = vui#component#vcontainer#new()

let child1 = vui#component#text#new('a')
let child2 = vui#component#text#new('b')
call vcontainer.add_child(child1)
call vcontainer.add_child(child2)
```

子要素に合わせて、動的に高さが調節されます。

### toggle

```vimscript
let screen = vui#screen#new()
let screen.state = { 'checked': 1 }

function! screen.on_before_render(screen) abort
  let state = a:screen.state
  let toggle = vui#component#toggle#new('text')
  call toggle.set_checked(state.checked)

  function! toggle.on_change(toggle) abort
    let b:screen.state.checked = !b:screen.state.checked
  endfunction

  call a:screen.set_root_component(toggle)
endfunction

call screen.show()
```

![vui-toggle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240412/fd624597-fec1-84fb-73d2-c70ae5cbf2f1.png)

チェックボックスです。

チェックボックス上で<kbd>Enter</kbd>を押すと、`on_change`関数が実行されます。
`on_change`の実行後に再描画が行われているようなので、この中で状態を更新しておく必要があります。

### button

```vimscript
let screen = vui#screen#new()
let screen.state = 0

function! screen.on_before_render(screen) abort
  let container = vui#component#vcontainer#new()
  let button = vui#component#button#new('+')
  let count = vui#component#text#new(string(a:screen.state))

  function! button.on_action(button) abort
    let b:screen.state += 1
  endfunction

  call container.add_child(button)
  call container.add_child(count)
  call a:screen.set_root_component(container)
endfunction

call screen.show()
```

## キーマッピング

```vimscript
function! screen.add_task() abort
  call s:do_something()
endfunction

function! screen.delete_item() abort
  let element = self.get_focused_element() 
  call s:do_something_with_element(element)
endfunction


call screen.map('t', 'add_task') " `t`が入力されたら、`screen.add_task`を実行する。
call screen.map('dd', 'delete_item') " `dd`が入力されたら、`screen.delete_item`を実行する。
call screen.show()
```

`screen.map`でキーマッピングをカスタマイズできます。

`screen.map`で登録したハンドラが実行される際は自動的に再描画が行われるようなので、
ハンドラ内で`screen.show`を実行する必要はありません。

## taskbook-vim

![taskbook-vim.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240412/c1af3301-1b69-0209-da40-e07c9ac8dbd1.png)

実際にVUIを使って[Taskbook](https://github.com/klaussinani/taskbook)をインタラクティブに操作するためのプラグインを作ってみました。

[taskbook-vim](https://github.com/uki00a/taskbook-vim)

## 参考

https://github.com/waldson/vui