在 EasyX 中使用 Nuklear UI 实现窗口和控件
一、Nuklear 简介
Nuklear 是著名的即时模式 GUI,类似 Dear ImGui,但 Nuklear 对后端的适应更灵活,性能要求也更低。
本文范例的执行效果如下:
二、Nuklear 基本用法及相关资源下载
Nuklear 及 EasyX 后端下载:nuklear_easyx.zip
只包括两个头文件,无需引用 lib 或 dll 文件:
- nuklear.h 主文件,实现窗口和控件逻辑
- nuklear_easyx.h 后端文件,实现 EasyX 的适配
使用方法非常简单,在代码里引用压缩包里的两个头文件即可。
Nuklear 官方项目:https://github.com/Immediate-Mode-UI/Nuklear
注意:和其它 STB-style headers 一样,Nuklear 头文件用预处理指令分为声明和定义两部分。使用时一个项目应该有且仅有一个源文件包含 Nuklear 的定义(#define NK_IMPLEMENTATION),不然就会在链接阶段报错。
三、使用范例
1. VS2022 下使用
Nuklear 要求字符串为 utf-8 编码,在 VS2022 里面需要设置一下中文格式为 utf-8,否则中文会乱码。
以下是上图界面的示例代码(注意 Nuklear 要求字符串为 utf8 编码):
#define NK_INCLUDE_FIXED_TYPES // 这个宏会引入 <stdint.h> 以帮助 nuklear 使用正确的数据类型
#define NK_INCLUDE_STANDARD_IO // 这个宏会引入 <stdio.h> 使得 nuklear 控件可以使用 printf 风格的 label
#define NK_INCLUDE_STANDARD_VARARGS // 这个宏会引入 <stdarg.h> 使得 nuklear 控件可以使用 vprintf 风格的 label
#define NK_INCLUDE_DEFAULT_ALLOCATOR // 这个宏会引入 <stdlib.h> 使得 nuklear 的使用者不需要关心内存分配
// nuklear 的设计目标是适配所有平台,以上几个宏是控制 nuklear 特性和行为的
// 不同平台基本数据类型大小不一样,指针长度不一样
// 有些平台(例如嵌入式)不提供 IO 功能,或者使用者希望 nuklear context 使用固定分配的内存等各种特殊需求
// 在 PC 平台上定义以上几个宏几乎是标准动作
#pragma warning(disable:4996) // nuklear 需要使用新版本 VS 默认禁止的 vsprintf 等旧版 C 函数
#define NK_IMPLEMENTATION // 包含 nuklear 的函数定义
#include "nuklear.h"
#define NK_EASYX_IMPLEMENTATION // 包含 nuklear easyx 后端的函数定义
#include "nuklear_easyx.h"
#pragma execution_character_set("utf-8") // VS 需要设定执行字符集为 utf8 格式,否则非英文字符会乱码
int main()
{
// 初始化图形窗口
initgraph(640, 480);
BeginBatchDraw();
// 初始化 GUI 环境
EasyxFont* font = nk_EasyxFont_create("新宋体", 16);
struct nk_context* ctx = nk_easyx_init(font);
int r = 20;
enum { OP_CIRCLE, OP_SQUARE };
int op = OP_CIRCLE;
COLORREF fc = RED;
while (true)
{
/* Events */
ExMessage m;
nk_input_begin(ctx);
while (peekmessage(&m))
{
nk_easyx_handle_event(&m);
}
nk_input_end(ctx);
/* GUI */
if (nk_begin(ctx, "例子", nk_rect(300, 50, 300, 200),
NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE))
{
nk_layout_row_static(ctx, 0, 80, 1);
if (nk_button_label(ctx, "变色"))
{
if (fc == RED)
fc = GREEN;
else
fc = RED;
}
nk_layout_row_dynamic(ctx, 0, 2);
if (nk_option_label(ctx, "圆形", op == OP_CIRCLE)) op = OP_CIRCLE;
if (nk_option_label(ctx, "方形", op == OP_SQUARE)) op = OP_SQUARE;
nk_layout_row_dynamic(ctx, 0, 1);
nk_property_int(ctx, "半径:", 0, &r, 100, 10, 1);
}
nk_end(ctx);
/* Draw */
setbkcolor(RGB(128, 128, 128));
cleardevice();
setfillcolor(fc);
if (op == OP_CIRCLE)
fillcircle(200, 200, r);
else
fillrectangle(200 - r, 200 - r, 200 + r, 200 + r);
nk_easyx_render(); // draw GUI
FlushBatchDraw();
}
// 清理 GUI 环境
nk_EasyxFont_del(font);
nk_easyx_shutdown();
// 关闭图形窗口
EndBatchDraw();
closegraph();
return 0;
}
2. MinGW 下使用(例如 VS Code)
vscode 支持 utf-8 编码,所以可以直接用前面 VS2022 的代码,忽略掉 #pragma execution_character_set("utf-8") 那行即可。
3. DevCpp 下使用
DevCpp 使用 MinGW,但 DevCpp 不支持 utf-8 编码,需要手动设置项目的编码为 utf-8。
四、备注
关于 C/C++ 中的字符集:
- 源码字符集:源文件是怎么编码的(毕竟源文件是个文本文件)
- 执行字符集:可执行文件里面字符串(代码里的字符串字面值)是怎么编码的
对源码字符集和执行字符集的识别和决定,不同编译器有不同的行为:
微软系列
新版编译器一般能自动识别源码字符集,执行字符集默认为本地字符集(例如 GBK)。可以使用编译指令/execution-charset:utf-8 或代码 #pragma execution_character_set("utf-8") 手动指定。
GCC 系列
源码字符集和执行字符集默认都为 utf-8,可用如下设置手动指定:
-finput-charset=GBK
-fexec-charset=UTF-8
C++11
无论使用什么源码或执行字符集,都可以使用 u8"字符串" 这种语法形式表示 utf-8 字面量。