インストールが簡単なRuby用のGUIライブラリlibuiのバインディングを作った話

## 追記

現在、LibUI上に構築された使いやすいDSL、[glimmer-dsl-libui](https://github.com/AndyObtiva/glimmer-dsl-libui) がAndy Maleh氏によって開発されました。素のLibUIで使いにくいところが大幅に改良されています。おすすめです。

## はじめに

こんにちは。

今日はRuby3.0がリリースされる日です。
先日libuiというRubyでGUIを作れるGemをリリースしました。
( [libui](https://github.com/andlabs/libui) のバインディングです)
まだまだ足りない部分は多いかと思いますが記事にします。

* https://github.com/kojix2/libui


| Windows | Mac | Linux |
|---------|-----|-------|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/ea88cb01-3754-599b-53d2-b3cc9f143103.png">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/a40e381c-5199-2a2e-abb5-9264973ca18a.png">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/8818444e-dbcc-625a-416c-bf3b265d98d4.png">|

## 背景とモチベーション

　これまでにもRubyのGUIライブラリは多数作られてきました。[Ruby/Tk](https://github.com/ruby/tk)、[Ruby/Gtk](https://github.com/ruby-gnome/ruby-gnome)、一世を風靡したShoes、Qt, FXRuby、ほかにもたくさん。

　しかし、Windows環境で手軽にGUIアプリケーションを作る方法が少ないという状況は続いていました。Ruby/GtkやRuby/Tkを利用する場合、TkやGTKといったツールキットのインストールが必要です。これは、初心者にとって障壁になります。また、ベテランの方でも、ユーティリティにさくっとGUIを追加して配布したいことはあると思います。この問題を解決するために、libuiというポータブルなGUIライブラリのバインディングを作成しました。

* https://github.com/kojix2/libui

　libuiは2020/12/25時点で、Ruby界で最もインストールが簡単なGUIライブラリだと思います（たぶんね）。

## libuiとは

　libuiは非常に軽量なC言語で書かれたマルチプラットフォームなGUIツールです。Rubyバインディングは、バージョン4.1に対応しています。

* https://github.com/andlabs/libui


## インストール

```sh
gem install libui
```

## 簡単なつかい方

下記に簡単な使用例を示します。

![entry.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/20a4e9c4-f689-e2cb-dbca-492c06df1916.gif)

```ruby
require 'libui'

UI = LibUI

UI.init

# ウィンドウを作成
main_window = UI.new_window('hello world', 300, 50, 1)
# ×ボタンでウィンドウを消せるようにする
UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.control_destroy(main_window)
  UI.quit
  0
end

# 配置用のボックス
hbox = UI.new_horizontal_box
# ボックスをウィンドウに追加
UI.window_set_child(main_window, hbox)

# 入力欄の作成
entry = UI.new_entry
# 文字が入力されたターミナルに表示
UI.entry_on_changed(entry) do
  puts UI.entry_text(entry).to_s
  $stdout.flush # リアルタイムにターミナルに表示
end
# ボックスに追加
UI.box_append(hbox, entry, 1)

# ボタン
button = UI.new_button('Button')
# ボタンが押されたら、ダイアログを出す
UI.button_on_clicked(button) do
  text = UI.entry_text(entry).to_s
  UI.msg_box(main_window, '入力した文字は次の通りです', text)
  0
end
# ボックスに追加
UI.box_append(hbox, button, 0)

# ウインドウを表示
UI.control_show(main_window)
UI.main
UI.quit
```

（irbやpryだとうまく実行できないのでご注意ください）

もう少し詳しいつかい方を知りたい方は、リポジトリのExamplesを見てください。

## 記法の一般的なルール

Cで書かれたオリジナルのlibuiと比較して、

* メソッド名はsnake_caseです。
* 最後の引数がnilの場合、省略できます。
* ブロックをコールバックとして渡すことができます。
    * ブロック内で明示的に0を返してください。
    * ブロックはProcオブジェクトに変換され、最後の引数に追加されます。
    * その場合でも、最後の引数nilを省略できます。


## オブジェクト指向じゃないの？

* 現時点では、オブジェクト指向ではありません。
  * 中途半端なオブジェクト指向アプローチを提供する代わりに、そのままにしています
  * 将来的にオブジェクト指向にしていくかは現時点では未定です。

## 既存のlibuiバインディングとの比較

libuiには、すでにRubyのバインディングが存在します。

* https://github.com/jamescook/libui-ruby

　しかし、このパッケージはインストールが面倒という弱点があります。libuiはこの点を解消しています。

* C言語の関数の呼び出しに、Ruby標準ライブラリのfiddleを用いて行っています。（追加のライブラリは不要です）
* libuiの共有ライブラリをGemの中に梱包しています。（libuiのインストールは不要です）
  * `libui.so`, `libui.dylib`, `libui.dll` です。
  * Gemのサイズを心配する人がいるかもしれませんがご安心ください。 全て合わせてもファイルサイズはたったの1.4MBです。Ruby/Tkと比較してもかなり軽量です。Windowsしか使わないという方は、あとから`libui.so`, `libui.dylib`を削除しても大丈夫です。この場合、`libui.dll` は269Kしかありません。とても軽量です。
* libui-rubyよりもRubyらしい記法を実現しています。特にブロック記法を用いたコールバックに対応しています。


## OCRAを使って実行ファイル(EXE)を作る方法

次の3つのdllを指定する必要があります。また、念の為にfiddleを確実に含めるようにしておきます

```msshell
ocra examples\control_gallery.rb        ^
  --dll ruby_builtin_dlls\libssp-0.dll  ^
  --dll ruby_builtin_dlls\libgmp-10.dll ^
  --dll ruby_builtin_dlls\libffi-7.dll  ^
  --gem-all=fiddle                      
```

さらに、必要に応じて次のようなオプションを追加します。詳しくはOCRAのマニュアルをご覧ください。

```msshell
  --window                              ^
  --add-all-core                        ^
  --chdir-first                         ^
  --icon assets\app.ico                 ^
  --verbose                             ^
  --output out\gallery.exe
```

#### おまけ 非同期処理しなきゃならないときにはdrubyというのがあります

　RubyでGUIを伴うデスクトップアプリケーションを作るというのは、Shoesの一時的なブームを除いて、あまり流行しない印象があります。その理由の一つは、GUI側と、Rubyのロジックの非同期処理が面倒くさいからだと思います。この場合、[druby](https://docs.ruby-lang.org/ja/latest/library/drb.html)を利用するのが一つの解決策になることがあります。参考まで。