OpenSiv3D+nuklearでGUIを実現する

# はじめに

こんにちは、たつのこです。
Qiita初投稿です。

最近、純C言語でいろいろやる仕事をしていました。
そこでnuklearというGUIライブラリを発見したので紹介したいと思います。

パブリックドメインなのでライセンス的には使いやすいはずなのですが
日本ではほぼ言及されていません。なぜだろう。

## Nuklearの特徴

![nuklearはこんな感じで動く.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/bc60ee96-6ee3-4347-0eb3-ed9dea64eaa6.png)

> [GitHub - vurtun/nuklear: A single-header ANSI C gui library](https://github.com/vurtun/nuklear)
> https://github.com/vurtun/nuklear

* シングルヘッダライブラリ
* C89で書かれている
* 他ライブラリへの依存性はなし
* 描画は行わず、ロジックのみ処理をする
* etc ...

nuklearで一番特徴的なのは、nuklear自身は画面への描画を一切行っていないところにあります。
どういうことかというと、nuklearがやってくれる仕事は以下の二つのどちらかです。

* 頂点バッファとテクスチャを出力する
* 描画命令リストを出力する

つまり、実際に画面へ表示してくれる人を別に用意する必要があります。

DirectXやOpenGLを直接触れる環境ならば、nuklearに頂点バッファとテクスチャを出力してもらえば動きます。

それ以外の環境、例えば、ゲーム用ライブラリを使っている場合などは、頂点バッファとテクスチャから描画を行うのは難しいのでnuklearに描画コマンドを出力してもらいます。
簡単に述べると、nuklearが「座標(x1,y1)から座標(x2,y2)まで#FFFFFFFF色の線を引いてほしいな！」みたいな図形描画の命令をリストアップしてくれるので、それに従って描画を行います。

ちなみに、nuklear公式githubのdemoフォルダ配下には以下のライブラリを利用した利用例があります。

* allegro5
* DirectX11
* GDI
* GDI++
* GLFW
* SDL
* SFML
* X11

# nuklearをOpenSiv3Dで使おう

さてここからが本題です。
先に述べたように、nuklearには画面に描画する機能を持っていませんので、本記事では描画のバックエンドとしてOpenSiv3Dを使っていきます。

何となく動くスニペットを作りましたので、ダウンロードしてご利用ください。
ライセンスはパブリックドメインの予定です。

> https://gitlab.com/snippets/1699359

~~Visual Studio 2017(15.5.6) + OpenSiv3D(0.2.2)で動かしています。~~

次の環境で確認しなおしました(スニペットも少し更新しました)。

* Visual Studio 2019(16.0.0)
* OpenSiv3D v0.3.2
* nuklear 4.00.2

このスニペットを導入すると、Visual Studio 2017のソリューションエクスプローラは以下の図のような感じになると思います。

![solutionexp.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/98ee0ff1-71ec-9666-97b2-ada3a6926ede.png)

動作する最低限のソースコードは以下です。

```c++
#include <Siv3D.hpp> // OpenSiv3D v0.2.2

#include "NkSiv3d.h"

#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_IMPLEMENTATION
#include "nuklear.h"

void Main()
{
    NkSiv3d nkSiv3d{12};
    struct nk_context* ctx = nkSiv3d.context();
    while (System::Update()) {
        nkSiv3d.update();
        //ここにコードを書く！
        nkSiv3d.render();
    }
}
```

次の章から、どんなことができるかをざっくり説明していきます。

## ウインドウを作る

ウインドウといっても、アプリケーション自体のウインドウはOpenSiv3Dが作りますので、アプリ内ウインドウ？みたいな感じになります。

```c++
if (nk_begin(ctx, "Demo", nk_rect(50, 50, 200, 200),
    NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
    NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE)) {
}
nk_end(ctx);
```

これを実行すると、ウインドウが表示されます。

![make_window.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/81079416-ab6d-ce5b-0aca-c7b253d950b7.png)

今後は、nk_begin()のifの中に処理を追加していく形になります。
nk_begin()とnk_end()が一対一で対応していない場合は、実行時エラーが発生するので注意が必要です。


## 文字を表示する

nuklearでは、文字表示には**nk_label**関数を利用します。ただし、nk_label単体では動作しません。先にレイアウトの作成が必要です。

```cpp
// ウインドウ作成
if (nk_begin(ctx, "Demo", nk_rect(50, 50, 200, 200),
        NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
        NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE)) {
    //レイアウト作成
    nk_layout_row_static(ctx, 40, 150, 1);
    //文字表示
    nk_label(ctx, "HELLO 世界！", NK_TEXT_ALIGN_LEFT);
}
nk_end(ctx);
```

![label.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/81d16282-2ef0-f156-797c-f7e78ef717de.png)

第三引数の値によって文字の表示位置が変わります。この例では**NK_TEXT_ALIGN_LEFT**を指定しているので左に寄っていますが、**NK_TEXT_ALIGN_RIGHT**を指定すれば右側に寄ります。

![left_align.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/fc290930-ae9a-6510-a92d-34021a915997.png)


## ボタンを表示する。

ボタン表示には**nk_button_label**関数を利用します。画像をボタンにしたい場合は**nk_button_image**関数があります。記号向けの**nk_button_symbol**関数なんてものもあります。

```cpp
// ウインドウ作成
if (nk_begin(ctx, "Demo", nk_rect(50, 50, 200, 200),
        NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
        NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE)) {
    //レイアウト作成
    nk_layout_row_static(ctx, 40, 150, 1);
    if (nk_button_label(ctx, "ボタン！")) {
        //ボタンが押されたときの処理をここに書く
    }
}
nk_end(ctx);
```

![button.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/5956b208-aaba-d570-2ed6-0202e5b7d34f.png)

## チェックボックス

ボタン表示には**nk_checkbox_label**関数を利用します。引数checkには、チェックが入っている状態ならば1が、入っていないならば0が入ります。nk_checkbox_label関数は状態が更新された場合のみにtrueを返却するので、ユーザ側で状態変更を監視しなくても大丈夫です。

```cpp
// ウインドウ作成
if (nk_begin(ctx, "Demo", nk_rect(50, 50, 200, 200),
        NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
        NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE)) {
    //レイアウト作成
    nk_layout_row_static(ctx, 40, 150, 1);
    static int check = 0;
    if (nk_checkbox_label(ctx, "Checkbox", &check)) {
        //状態が変わったときの処理をここに書く
    }
}
nk_end(ctx);
```

![check.PNG](https://qiita-image-store.s3.amazonaws.com/0/7029/7b2aab5b-ef63-fc5d-bfe2-38c90b9ccf85.png)

# まとめ

* OpenSiv3Dでnuklearが動く！
* nk_beginとnk_endの間でいろいろやる！

## 問題点
今回の方法でOpenSiv3D+nuklearを利用した場合、いくつか問題点があります。

* nuklearはCなので、そのままだと使いにくい
* 日本語の入力ができない(表示はできる)
* 重い(デバッグビルドだとやばい)
    * ↑mapで文字列キャッシュしたら少しマシになりましたがうーん

一つ目の問題点は、頑張ってラップするクラスを作ればよいのですが、二つ目の解決は難しいかなと思っています。
誰かアドバイスプリーズ！

# 参照

* [OpenGL な UI を作るライブラリ一覧](https://qiita.com/shimacpyon/items/e5d2a4e2019273345c37)