Is Nuklear something specific? It has a small size (about 15 thousand lines of code), is fully contained in one header file, was created with an emphasis on portability and ease of use. Public Domain license.
I often have problems to solve which I have to write small utilities in several hundred lines of code. Usually, the result is a console application, which no one can really use except me. Can a simple GUI make these utilities more convenient?
The requirements to the result:
Will Nuklear succeed?
For example, let’s look at the creation of the utility dxBin2h - it reads the file byte by byte and writes it as a C-array. The program has some sorts of “buns”, such as removing unnecessary characters etc. Usually, small utilities are created for the sake of a third-party functionality… For example, dxBin2h was created for Winter Novel, for preprocessing ASCII files.
There should not exist any problems with the simplicity of development, should there? After all, the library was created with a focus on the simplicity. You can find simple example directly in Readme on GitHub. Absolutely clear and concise 20 lines of code give a beautiful and clear result.
/* init gui state */
struct nk_context ctx;
nk_init_fixed(&ctx, calloc(1, MAX_MEMORY), MAX_MEMORY, &font);
enum {EASY, HARD};
int op = EASY;
float value = 0.6f;
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);
But not everything is so simple. The GUI calculation part is really simple. But there must be a renderer too. Go to the demo folder, choose the one you like. I’m sure it will not be exactly 20 lines, not even 200. Moreover, all examples draw approximately the same result, but the code is significantly different because of the render.
Example of WinAPI initialization for Nuklear:
static LRESULT CALLBACK
WindowProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
if (nk_gdip_handle_event(wnd, msg, wparam, lparam))
return 0;
return DefWindowProcW(wnd, msg, wparam, lparam);
}
int main(void)
{
GdipFont* font;
struct nk_context *ctx;
WNDCLASSW wc;
RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
DWORD style = WS_OVERLAPPEDWINDOW;
DWORD exstyle = WS_EX_APPWINDOW;
HWND wnd;
int running = 1;
int needs_refresh = 1;
/* Win32 */
memset(&wc, 0, sizeof(wc));
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandleW(0);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"NuklearWindowClass";
RegisterClassW(&wc);
AdjustWindowRectEx(&rect, style, FALSE, exstyle);
wnd = CreateWindowExW(exstyle, wc.lpszClassName, L"Nuklear Demo",
style | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top,
NULL, NULL, wc.hInstance, NULL);
Example of SDL initialization:
int
main(int argc, char* argv[])
{
/* Platform */
SDL_Window *win;
SDL_GLContext glContext;
struct nk_color background;
int win_width, win_height;
int running = 1;
/* GUI */
struct nk_context *ctx;
/* SDL setup */
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0");
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
win = SDL_CreateWindow("Demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL
|SDL_WINDOW_SHOWN|SDL_WINDOW_ALLOW_HIGHDPI);
glContext = SDL_GL_CreateContext(win);
SDL_GetWindowSize(win, &win_width, &win_height);
Well, let’s take the SDL2 with OpenGL renderer and get the resulting application for Windows, Linux, Mac OS X, Android, iOS etc! All is super. The only problem here is that SDL is not presented in standard Windows. So you have to drag along with you. This violates the first requirement (small size), because the SDL itself weighs about a megabyte.
But in the examples you can see GDI+, which is in Windows starting with XP. GDI+ can TTF-fonts, PNG and JPG pictures, load resources from memory. So there will be 2 possible renderers: GDI+ for Windows and SDL for all other cases. You can place a piece of rendering-dependent code into a separate C-file (nuklear_cross.c). Then the main code will not be overloaded and it will be possible to focus on the interface, which greatly simplifies development. An additional advantage is compilation acceleration - Nuklear will be compiled into a separate object file which will rarely change.
Windows, GDI+ renderer, Arial 12pt font:
Linux, SDL2 with OpenGL renderer, default font:
The application looks very different! And the first thing that catches your eye is the font.
You need to use the same font to make the application look the same on all operating systems. It would be great to take any system font which is guaranteed to be everywhere. But there is no such a font. Therefore, the font must be included in the application. Ttf-fonts usually occupy hundreds of kilobytes. But you can create subsets with the necessary symbols only. FontSquirrel web service is a good start. DejaVu Serif was stuck up to 40kb, although it contains Cyrillic, Polish and a whole bunch of languages.
Everything would be fine, but GDI+ driver for Nuklear could not load a font from memory, only from a file. I had to correct… By the way, you can include the font into your application with the dxBin2h.
Windows, DejaVu Serif:
Linux, DejaVu Serif:
It’s much better. But I do not like the look of the checkboxes. And I would like to see pictures.
Both SDL2 and GDI+ are able to load pictures. But there is an additional dependency for SDL when loading JPG and PNG - SDL_image. It is pretty simple to get rid of: use stb_image.h if the project is compiling with SDL.
Not everything was good with GDI+ either. Namely, the GDI+ driver for Nuklear was not able to render images using GDI+. I had to implement it myself (Pull Request). Now everything is fixed and the code is in the official repository.
The code for loading the image via stb_image for OpenGL:
struct nk_image dxNkLoadImageFromMem(const void* buf, int bufSize){
int x,y,n;
GLuint tex;
unsigned char *data = stbi_load_from_memory(buf, bufSize, &x, &y, &n, 0);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
return nk_image_id((int)tex);
}
There is a mechanism for setting styles in Nuklear to change the look of the check boxes. Enabled and disabled check boxes are separate PNG images. The red theme from Nuklear examples (style.c file) is set:
nk_image checked = dxNkLoadImageFromMem(
(void*)checked_image, sizeof(checked_image) );
nk_image unchecked = dxNkLoadImageFromMem(
(void*)unchecked_image, sizeof(unchecked_image) );
set_style(ctx, THEME_RED);
{struct nk_style_toggle *toggle;
toggle = &ctx->style.checkbox;
toggle->border = -2; /* cursor must overlap original image */
toggle->normal = nk_style_item_image(unchecked);
toggle->hover = nk_style_item_image(unchecked);
toggle->active = nk_style_item_image(unchecked);
toggle->cursor_normal = nk_style_item_image(checked);
toggle->cursor_hover = nk_style_item_image(checked);
}
The application in Windows looks like this:
Linux:
The application looks a little different on different operating systems. However, the differences are insignificant, the result satisfied me. Nuklear is not in the category “I’m sure it will work everywhere and without testing.” But it is in the category “If that is necessary - I can easily finish it”.
ARTICLES
articles linux c gui tiny nuklear opengl
Dialogue & Discussion
TonyD
Sunday, March 4th, 2018, 04.38pm
It took me a while to realize what nuklear is.
Most graphics libraries try to provide a portable interface for the application developer - so your sdl or gl code can be highly portable. But nuklear doesn’t do that. Instead, nuklear tries to make the library itself easy to port to different display devices - at the cost to having zero portability for the application developer. So nuklear is really something optimized for developers to use with custom output devices - since you can customize it for your output device. It is also quite small - and that is a major benefit for those doing embedded work.
For the typical programmer with access to a graphics library, it is hard to see any benefit to using nuklear. (I’m investigating using it for a resource constrained application - where size matters a lot.)
RDRush
Tuesday, November 20th, 2018, 07.11am
Agree that Nuklear is optimal for small form factors requiring or simply benefiting from a GUI, but the library being small and providing most all common controls in a single package make GUI development very simplified. This level of simplification at a cost of a single header file is cost effective and easily maintainable; as was stated in the article, the majority of time may now be directed at developing the graphic interface rather than the setup and implementation associated with some and being capable of integration with SDL2 and OpenGL with almost no effort is definitely a bonus considering cross-platform, including mobile, development. It, Nuklear, is certainly versatile and could be used in applications as well as games allowing a stronger focus on brand and availability.
Excellent introduction and demonstration concerning Nuklear!
rt2
Friday, February 15th, 2019, 05.06pm
How draw directly on window? can You add simple example to draw polygon with hole or other primitives (line, rectangle etc.) big window inside main windo is not good idea. any click outside and losting every windows
DeXPeriX
Monday, February 18th, 2019, 06.37pm
rt2, you can use drawing backend (OpenGL for example) to draw something on the scene. The article is not about that, but about Nuklear. I do not feel myself advanced enough to write an OpenGL tutorial. But you can google ones. Your OpenGL code will be somewhere nearby clearing the scene with the color.
Leave a comment