はじめに
2018/12/27追記
- GetDrawStringWidthToHandle関数が今後利用できる旨を追記しました (Thanks @yumetodo さん)
- テキスト描画に利用している関数をDrawStringToHandleからDrawNStringToHandleに変更しました
2018/12/21追記
- 文中でGetDrawFormatStringWidthToHandle関数を利用していた箇所をGetDrawStringWidthToHandle関数へ変更しました(Thanks @8127 さん)
- @yumetodo さんの指摘を記事に反映しました。
- 記事末尾に、実際に書いたソースコードのすべてを追記しました。
この記事はDxLib Advent Calendar 2018 の19日目の記事です。
さて、DXライブラリを使っていて、困ること、ありませんか?
ありますよね?
そう、DXライブラリには、GUIがないのです。
GUIを自力でがりがりと書くのは割と大変で、心が折れることもしばしば、、、
この記事では、nuklearというGUIライブラリをDXライブラリに導入して
GUI作成を楽にすることを目指します。
nuklearで、快適なGUIライフを!
※OpenSiv3D版は こちら
環境
この記事は、以下のバージョンのいろいろなものを利用して執筆されました。
- DXライブラリ : Ver 3.19d
- nuklear : 4.00.2(2018/10/31)
- Visual Studio 2017 : 15.9.4
準備編
まず、準備が必要です。
- DXライブラリ置き場 使い方説明 の手順に従ってDXライブラリの準備をする
- GitHub - vurtun/nuklear: A single-header ANSI C gui library からnuklear.hをダウンロードする。
- nuklear.hをプロジェクトに追加する。
ヘッダオンリーライブラリなので、導入がとても簡単なのが一つのメリットですね。
実装編
nuklearの初期化
nuklearを実行するためには、まず初期化する必要があります。
nuklearの初期化では、主にフォントサイズの設定を行います。
// nuklearの初期化
struct nk_context ctx;
const auto dx_font = CreateFontToHandle(nullptr, 13, 1);
struct nk_user_font nk_font;
// フォントハンドル
nk_font.userdata = nk_handle_id(dx_font);
// フォントの高さ
nk_font.height = static_cast<float>(13);
// 文字列の幅を計算する関数を登録
nk_font.width = [](const nk_handle handle, float h, const char* str, const int len) -> float {
return static_cast<float>(GetDrawStringWidthToHandle(str, len, handle.id));
};
nk_init_default(&ctx, &nk_font);
基本的に、nk_user_font構造体にいろいろ値を入れてnk_init_default関数に突っ込めばよいのですが、
一つだけ注意点があります。
nk_user_font構造体のwidthに突っ込む関数の引数にchar* strがありますが、
こいつがゼロ終端されていないので、lenを用いてちゃんと長さを指定する必要があります。
strがゼロ終端されていると思い込んでしまうと、なんか変になります。
今回利用しているGetDrawStringWidthToHandle関数は第二引数に文字列長を指定するので大丈夫かと思いきや
yumetodoさんの指摘によると大丈夫じゃないようです。
↑DXライブラリの次バージョンで対応されるため大丈夫になりました!
入力処理
ここからは、初期化の後、メインループで行う処理に入っていきます。
まず最初に、nuklearに現在の入力を教えてあげます。
nk_input_beginとnk_input_endの間で、入力を教える関数を実行します。
// nuklearの入力処理
nk_input_begin(&ctx);
auto mouse_x = 0;
auto mouse_y = 0;
const auto click = GetMouseInput();
GetMousePoint(&mouse_x, &mouse_y);
// マウスの位置を教える
nk_input_motion(&ctx, mouse_x, mouse_y);
// マウスのクリック状態を教える
nk_input_button(&ctx, NK_BUTTON_LEFT, mouse_x, mouse_y, (click & MOUSE_INPUT_LEFT) != 0);
nk_input_button(&ctx, NK_BUTTON_RIGHT, mouse_x, mouse_y, (click & MOUSE_INPUT_RIGHT) != 0);
nk_input_button(&ctx, NK_BUTTON_MIDDLE, mouse_x, mouse_y, (click & MOUSE_INPUT_MIDDLE) != 0);
// マウスホイールの状態を教える
nk_input_scroll(&ctx, nk_vec2(GetMouseHWheelRotVolF(), GetMouseWheelRotVolF()));
nk_input_end(&ctx);
ここでは、簡略化のためにマウスの入力だけ渡しているのですが
テキスト入力を使わないのであれば、マウスの入力だけで何とかなります。
描画処理
次は描画を行います。
nuklearが発行するコマンドをforループで消化していきます。
この部分がキモであり、一番重要なのですが、長いので一部だけ紹介します。
すべてのソースコードは最後に張り付けます!
const struct nk_command* cmd;
// nuklearの描画
nk_foreach(cmd, &ctx) {
switch (cmd->type) {
case NK_COMMAND_NOP:
break;
case NK_COMMAND_SCISSOR: {
const auto s = reinterpret_cast<const struct nk_command_scissor*>(cmd);
SetDrawArea(s->x, s->y, s->x + s->w, s->y + s->h);
} break;
case NK_COMMAND_LINE: {
const auto l = reinterpret_cast<const struct nk_command_line *>(cmd);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, l->color.a);
DrawLine(l->begin.x, l->begin.y, l->end.x, l->end.y, GetColor(l->color.r, l->color.g, l->color.b));
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
} break;
case NK_COMMAND_RECT: {
const auto r = reinterpret_cast<const struct nk_command_rect *>(cmd);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, r->color.a);
DrawBox(r->x, r->y, r->x + r->w, r->y + r->h, GetColor(r->color.r, r->color.g, r->color.b), FALSE);
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
} break;
case NK_COMMAND_RECT_FILLED: {
const auto r = reinterpret_cast<const struct nk_command_rect_filled *>(cmd);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, r->color.a);
DrawRoundRect(r->x, r->y, r->x + r->w, r->y + r->h, r->rounding, r->rounding, GetColor(r->color.r, r->color.g, r->color.b), TRUE);
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
} break;
case NK_COMMAND_CIRCLE: {
const auto c = reinterpret_cast<const struct nk_command_circle *>(cmd);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, c->color.a);
DrawOval(c->x + c->w / 2, c->y + c->h / 2, c->w / 2, c->h / 2, GetColor(c->color.r, c->color.g, c->color.b), FALSE, c->line_thickness);
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
} break;
case NK_COMMAND_CIRCLE_FILLED: {
const auto c = reinterpret_cast<const struct nk_command_circle_filled *>(cmd);
SetDrawBlendMode(DX_BLENDMODE_ALPHA, c->color.a);
DrawOval(c->x + c->w / 2, c->y + c->h / 2, c->w / 2, c->h / 2, GetColor(c->color.r, c->color.g, c->color.b), TRUE);
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
} break;
case NK_COMMAND_TEXT: {
const auto t = reinterpret_cast<const struct nk_command_text*>(cmd);
const auto font_handle = t->font->userdata.id;
SetDrawBlendMode(DX_BLENDMODE_ALPHA, t->foreground.a);
DrawNStringToHandle(t->x, t->y, static_cast<const char*>(t->string), t->length, GetColor(t->foreground.r, t->foreground.g, t->foreground.b), font_handle);
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
} break;
//他にもあるけど省略、、、
default:
break;
}
}
nk_clear(&ctx);
nuklearを実際に使うところ
ここまでで、ようやっとnuklearを利用する準備が整いました。
ハローワールド的な簡単なウインドウを表示してみます。
※はじめにに張り付けている画像を表示するためのコードです。
// nuklearのレイアウト作成
enum { EASY, HARD };
static int op = EASY;
static float value = 0.6f;
static int i = 20;
if (nk_begin(&ctx, "Show", nk_rect(50, 50, 220, 220),
NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_CLOSABLE)) {
/* fixed widget pixel width */
nk_layout_row_static(&ctx, 30, 80, 1);
if (nk_button_label(&ctx, "button")) {
/* event handling */
}
/* fixed widget window ratio width */
nk_layout_row_dynamic(&ctx, 30, 2);
if (nk_option_label(&ctx, "easy", op == EASY)) op = EASY;
if (nk_option_label(&ctx, "hard", op == HARD)) op = HARD;
/* custom widget pixel width */
nk_layout_row_begin(&ctx, NK_STATIC, 30, 2);
{
nk_layout_row_push(&ctx, 50);
nk_label(&ctx, "Volume:", NK_TEXT_LEFT);
nk_layout_row_push(&ctx, 110);
nk_slider_float(&ctx, 0, &value, 1.0f, 0.1f);
}
nk_layout_row_end(&ctx);
}
nk_end(&ctx);
まとめ
駆け足でしたが、nuklearをDXライブラリで利用する方法でした。
本記事で解説した部分のソースコードはgitlabのスニペットに上げています。
ライセンスはパブリックドメインの予定なので、煮るなり焼くなりご自由にどうぞ。
次はスタイルもしくは、、、nuklearのウィジェット一覧を、書き、、、書きたいです、、、
また、本記事を書くためにお試しで書いていたすべてのソースコードを以下に上げました。
今回省略したテキスト入力などの部分も全部書いてあります(すごく汚いですけど、、、、)
Tatsunoko / dxnk · GitLab