Nuklear is a library for creating immediate mode user interfaces. The library does not have any dependencies (only C89! Only hardcore!), But it does not know how to create windows of the operating system or perform real rendering. Nuklear is an embedded library that provides user-friendly interfaces for drawing with the tools of the implemented application. There are examples on WinAPI, X11, SDL, Allegro, GLFW, OpenGL, DirectX. The parent concept was the ImGUI library.
What is beautiful exactly Nuklear? It has a small size (about 15 thousand lines of code), is completely contained in one header file, it was created with an emphasis on portability and ease of use. Public Domain License.
I often have tasks for which implementation I have to write small utilities of several hundred lines of code. Usually the result is a console application that no one else can use besides me. Perhaps a simple GUI could make these utilities more convenient?
So, the requirements for the result:
For example, consider the creation of the utility dxBin2h ( GitHub ) - it reads the file byte-by-byte and writes it as a C-array. In addition to the main functionality, the program has all sorts of "buns", such as the removal of unnecessary characters, etc. Usually for the sake of third-party functionality and create their own small utilities. For example, dxBin2h was created for Winter Novel , for preprocessing ASCII files.
Well, with what, and with ease of development problems should not be. After all, the library was created with an eye on it, right? Right in the GitHub Readme is an example. 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 part directly responsible for rendering the GUI is really simple. Only there should be a render. Go to the demo folder, select the one you like. And we already see not 20 lines. Not only that, although the examples draw approximately the same result on the screen, the code differs significantly because of the render.
WinAPI:
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);
SDL:
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, take the SDL2 render with OpenGL and get the resulting application for Windows, Linux, Mac OS X, Android, iOS and a whole lot more! Everything is super, only here in the standard Windows distribution there is no SDL library. So you have to carry with you. And this violates the first requirement (small size), because SDL itself weighs about a megabyte.
But in the list of examples you can see GDI +, which is in Windows since XP. GDI + can ttf-fonts, pictures PNG and JPG, and all this can be downloaded directly from memory. Let in the end there will be 2 possible render: GDI + for Windows and SDL for all other cases. You can make a part of the code, depending on the render, in 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 the development. An additional advantage is the acceleration of compilation - the entire Nuklear will be compiled into a separate object file, which will rarely change.
Windows drawing via GDI +, Arial 12pt font:
Linux, drawing via SDL2 and OpenGL, default font:
The app looks completely different! And the first thing that catches your eye is the font.
To make the application look the same on all operating systems, you need to use the same font. It would be possible to take some kind of system font, which is guaranteed to be everywhere. But there is no such font. Therefore, the font will have to be included in your application. ttf-fonts usually weigh hundreds of kilobytes, but subsets with the necessary characters are well created from them. For example, using the web service FontSquirrel . DejaVu Serif shrank to 40kb, although it contains Cyrillic, Polish and a bunch of languages.
Everything would be great, but the GDI + driver for Nuklear could not load the font from memory, only from a file. I had to fix it ... By the way, the font can be included in your application using the same dxBin2h .
Windows DejaVu Serif:
Linux, DejaVu Serif:
Already much better. But I do not like the look of checkboxes. And I would like to see pictures.
Both SDL2 and GDI + can upload pictures. But for SDL, when loading JPG and PNG, an additional dependency appears - SDL_image. Getting rid of it is quite simple: use stb_image.h if the project is going with SDL.
Not everything was good with GDI + either. Namely, the GDI + driver for Nuklear did not know how to draw images using GDI +. I had to delve into working with images and implement myself ( Pull Request ). Now everything is fixed and the code in the official repository.
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); }
To change the look of checkboxes in Nuklear there is a style setting mechanism. Here the on and off checkbox are separate PNG images. In the same code, a red theme is exhibited from the examples of Nuklear (file style.c ):
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); }
A Windows application looks like this:
In Linux:
The application looks a little different in different operating systems. However, the differences are insignificant, the result I was satisfied. Nuklear is not included in the category "I am sure it will work everywhere and without testing." But it is included in the category "if anything is needed, I will add it easily."
UPD : Added a web demo, now the library can be viewed live online.
Source: https://habr.com/ru/post/319106/