⬆️ ⬇️

Is Nuklear the perfect GUI for micro projects?

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.



Nuklear web demo



Formulation of the problem



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:



  1. Small size, up to hundreds of kilobytes.
  2. Cross-platform, for starters at least Windows and Linux.
  3. No dependency on external libraries in Windows, everything should be in a single EXE file.
  4. Decent / beautiful appearance.
  5. Support for pictures in JPG and PNG.
  6. Ease of development, the ability to develop in Windows and Linux.

    Will Nuklear cope?


Nuklear NodeEdit



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.



Ease of development, cross-platform



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.



Sample code
/* 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 and SDL initialization example

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); 


No dependencies in Windows



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:



dxBin2h GDI + Arial 12pt



Linux, drawing via SDL2 and OpenGL, default font:



dxBin2h SDL2 linux stdfont



The app looks completely different! And the first thing that catches your eye is the font.



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:



dxBin2h Windows DejaVu Serif



Linux, DejaVu Serif:



dxBin2h Linux DejaVu Serif



Already much better. But I do not like the look of checkboxes. And I would like to see pictures.



Pictures: PNG, JPG



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.



Image upload code 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); } 


Appearance of the application



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:



dxBin2h Windows



In Linux:



dxBin2h Linux



What is the result?



  1. Windows EXE after compiling 200kb, after stinging UPX 90kb. In Linux, due to the use of stb_image, the size of the application is on average 100kb larger.
  2. Checked work in Windows and Linux.
  3. Font and images are stored as arrays in the application's memory. There is no dependency on WinAPI in Windows.
  4. The application style change engine is working.
  5. PNG and JPG are loaded with GDI + and stb_image.
  6. All the dirty platform dependent code is in a separate file. The developer focuses on creating the application.


Known Issues





How to use practices



  1. Clone the repository https://github.com/DeXP/nuklear_cross into your project
  2. Connect "nuklear_cross / nuklear_cross.h", use functions from there


Conclusion



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."



useful links





UPD : Added a web demo, now the library can be viewed live online.



')

Source: https://habr.com/ru/post/319106/



All Articles