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 also does not know how to create windows of the operating system or perform real rendering. Nuklear is an embeddable library that provides convenient interfaces for rendering using an implemented application. There are examples on WinAPI, X11, SDL, Allegro, GLFW, OpenGL, DirectX. The parent of the concept was the ImGUI library .


    Why is Nuklear beautiful? It has a small size (about 15 thousand lines of code), is fully contained in one header file, was created with emphasis on portability and ease of use. Public Domain License.


    Nuklear Web Demo


    Formulation of the problem


    I often have tasks for which I have to write small utilities in several hundred lines of code. Usually, the result is a console application that no one else can really use. Perhaps a simple GUI can make these utilities more convenient?


    So, the requirements for the result:


    1. Small size, up to hundreds of kilobytes.
    2. Cross-platform, for a start, at least Windows and Linux.
    3. The absence of dependence on external libraries in Windows, everything should be in one EXE-file.
    4. Decent / beautiful appearance.
    5. Support for pictures in JPG and PNG formats.
    6. Ease of development, the ability to develop on Windows and Linux.
      Will Nuklear do it?

    Nuklear NodeEdit


    For example, consider the creation of the dxBin2h utility ( GitHub ) - it reads the file byte by byte and writes it in the form of a C-array. In addition to the main functionality, the program has all sorts of "goodies", such as deleting unnecessary characters, etc. Usually, for the sake of third-party functionality, their own small utilities are created. For example, dxBin2h was created for Winter Novel , for preprocessing ASCII files.


    Ease of development, cross-platform


    Well, with what, and with the simplicity of the development of problems should not be. After all, with an eye on it, the library was created, right? There ’s an example right in the Readme on GitHub . Absolutely clear and concise 20 lines of code give a beautiful and clear result.


    Example 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 so simple. The part directly responsible for rendering the GUI is really simple. Only there should also be a render. We go to the demo folder , select the one we like. And we see far from 20 lines. Not only that, although the examples draw about the same result on the screen, the code is significantly different precisely because of the render.


    Initialization example on WinAPI and SDL

    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 on Windows


    Well, take a SDL2 renderer with OpenGL and get the resulting application for Windows, Linux, Mac OS X, Android, iOS, and a lot more! Everything is super, but there is no SDL library in the standard Windows package. So, you have to drag along. 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 on Windows since XP. GDI + can do ttf fonts, PNG and JPG images, and all this can be loaded directly from memory. Let it end up with 2 possible renderings: GDI + for Windows and SDL for all other cases. You can move the part of the code depending on the render to 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 plus is compilation acceleration - the whole Nuklear will be compiled into a separate object file, which will rarely change.


    Windows rendering via GDI +, Arial 12pt font:


    dxBin2h GDI + Arial 12pt


    Linux, rendering via SDL2 and OpenGL, default font:


    dxBin2h SDL2 linux stdfont


    The application looks very 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. One could take some 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 FontSquirrel web service . DejaVu Serif has shrunk to 40kb, although it contains Cyrillic, Polish and a bunch of languages.


    Everything would be fine, but the GDI + driver for Nuklear could not load a 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 don't like the look of checkboxes. And I would like to see pictures.


    Images: PNG, JPG


    Both SDL2 and GDI + can load 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 being built with SDL.


    With GDI +, not everything was good either. Namely, the GDI + driver for Nuklear was not able to draw images using GDI +. I had to delve into the work with images and implement it 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);
    }

    App appearance


    To change the look of checkboxes, Nuklear has a style-setting mechanism. Here, the on and off checkbox are separate PNG images. In the same code, the red theme from the Nuklear examples (file style.c ) is exposed :


        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


    On Linux:


    dxBin2h Linux


    What is the result?


    1. Windows EXE after compiling 200kb, after squeezing UPX 90kb. On 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 pictures are stored as arrays in the application memory. There are no dependencies not on WinAPI in Windows.
    4. The application style change engine works.
    5. PNG and JPG are loaded using GDI + and stb_image.
    6. All the dirty platform-specific code is moved to a separate file. The developer focuses on creating the application.

    Известные проблемы


    • Различное сглаживание шрифтов в разных операционных системах
    • Разный размер чекбоксов
    • Разная поддержка изображений (при использовании stb_image нужно избегать проблемных изображений)
    • Не полная поддержка юникода при урезанном шрифте
    • Нет примера на технологиях Mac OS X

    Как пользоваться наработками


    1. Склонировать репозиторий https://github.com/DeXP/nuklear_cross в свой проект
    2. Подключить "nuklear_cross/nuklear_cross.h", использовать функции оттуда

    Заключение


    Приложение выглядит немного по-разному в разных операционных системах. Однако отличия незначительны, полученный результат меня удовлетворил. Nuklear не входит в категорию "я уверен, что будет работать везде и без тестирования". Зато входит в категорию "если что будет нужно — легко допишу".


    Полезные ссылки



    UPD : Added a web demo, now you can watch the library live online.


    Also popular now: