Nuklear Immediate-Mode UI Toolkit

repository·master·Indexed 11 days ago

https://github.com/immediate-mode-ui/nuklear

A minimal-state, immediate-mode graphical user interface toolkit written in ANSI C. Designed as a modular, embeddable library, Nuklear focuses on UI logic and requires the host application to handle rendering and input. It supports various backends including GLFW, SDL, SFML, Vulkan, and OpenGL ES, and can be integrated as a single-header library using the NK_IMPLEMENTATION macro.

Tokens
5.9K
Snippets
17
Records
28
Agent score
95%

What's inside Nuklear

  1. Prevent widgets from being covered by the keyboard

    master

    On mobile devices, the soft keyboard may cover widgets located near the bottom of the screen.

    A recommended pattern to handle this is:

    1. Make all text edit widgets view-only.
    2. When a view-only widget is tapped, dynamically create a new input widget in a position above the keyboard.
    3. When the keyboard is dismissed, copy the input from the dynamic widget back to the original view-only widget and destroy the dynamic one.
  2. How the Nuklear application lifecycle works

    master

    Nuklear is an immediate-mode UI toolkit that does not handle rendering or OS windowing/input directly. Instead, it follows a three-step loop every frame:

    1. Input Mirroring: Capture your platform's input (mouse motion, button presses, keyboard) and pass it to Nuklear using nk_input_begin(&ctx) and nk_input_end(&ctx).
    2. UI Building: Define your interface using Nuklear's layout and widget APIs (e.g., nk_begin, nk_button_label, nk_slider_float).
    3. Drawing: Iterate through the draw commands generated by Nuklear using nk_foreach and pass those commands to your specific rendering backend (e.g., OpenGL, Vulkan, Direct3D). Finally, call nk_clear(&ctx) to reset the command list for the next frame.
    /* 1. Setup (once) */
    struct nk_context ctx;
    // ... initialize ctx with nk_init_fixed ...
    
    while (running) {
        /* 2. Input (every frame) */
        nk_input_begin(&ctx);
        // ... mirror input ...
        nk_input_end(&ctx);
    
        /* 3. Build UI (every frame) */
        if (nk_begin(&ctx, "Window", nk_rect(50, 50, 220, 220), NK_WINDOW_BORDER)) {
            if (nk_button_label(&ctx, "Click Me")) { /* handle click */ }
        }
        nk_end(&ctx);
    
        /* 4. Draw (every frame) */
        const struct nk_command *cmd;
        nk_foreach(cmd, &ctx) {
            // ... dispatch cmd->type to your renderer ...
        }
        nk_clear(&ctx);
    }
  3. Build nuklear.h using the File Packer

    master

    The nuklear.h header file is generated by the File Packer utility. To build the header, execute the appropriate script for your operating system from the repository root:

    • Linux/Mac: Run ./paq.sh
    • Windows: Run paq.bat
    # Linux/Mac
    ./paq.sh
    
    # Windows
    paq.bat
  4. Basic usage pattern for Nuklear widgets

    master

    Nuklear uses an immediate-mode paradigm where UI elements are declared and handled within a window block. The typical lifecycle involves:

    1. Initializing the context with nk_init_fixed.
    2. Starting a window with nk_begin.
    3. Defining a layout (static, dynamic, or custom) using nk_layout_row_* functions.
    4. Calling widget functions (e.g., nk_button_label, nk_slider_float) which return true if an interaction occurred.
    5. Ending the window with nk_end.

    Layouts determine how widgets are positioned:

    • nk_layout_row_static: Uses fixed pixel dimensions for widgets.
    • nk_layout_row_dynamic: Uses a ratio of the window width for widgets.
    • nk_layout_row_begin/push/end: Allows for complex, custom-sized widget rows.
    // init gui state
    enum {EASY, HARD};
    static int op = EASY;
    static float value = 0.6f;
    static int i =  20;
    struct nk_context ctx;
    
    // Initialize context
    nk_init_fixed(&ctx, calloc(1, MAX_MEMORY), MAX_MEMORY, &font);
    
    // Start a window
    if (nk_begin(&ctx, "Show", nk_rect(50, 50, 220, 220), 
        NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_CLOSABLE)) {
        
        // 1. Fixed widget pixel width
        nk_layout_row_static(&ctx, 30, 80, 1);
        if (nk_button_label(&ctx, "button")) {
            // event handling
        }
    
        // 2. 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;
    
        // 3. Custom widget pixel width using row push
        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);
    }
    k_end(&ctx);
  5. Compile the SFML 2.4 nuklear demo

    master

    To compile the demo, you must modify the SFML_DIR variable in the provided Makefile. Set SFML_DIR to the absolute path of your SFML installation root (the directory containing the lib and include folders).

    Linux Requirements: You must install the udev development files on your system to successfully build the demo on Linux.

    # Example Makefile modification
    SFML_DIR = /path/to/your/sfml_installation_root
  6. Build and run the OpenGL ES demo on Raspberry Pi

    master

    The demo supports accelerated OpenGL ES2 output on Raspberry Pi. However, because the default SDL2 in Raspbian repositories often lacks the rpi video driver, you may need to compile SDL2 yourself (ideally with the --disable-video-x11 configure option) or manually set the video driver environment variable.

    To build the demo specifically for Raspberry Pi, use:

    make rpi

    If you have already compiled SDL2, you can attempt to run the application by exporting the driver variable first:

    export SDL_VIDEODRIVER=rpi
  7. Integrate Nuklear with SDL and Vulkan

    master

    The Nuklear SDL Vulkan integration operates by rendering the UI to independent render targets (overlay images). The application is responsible for managing these targets, ensuring they are correctly sized and resized.

    Key Requirements:

    • You must manage the lifecycle and sizing of the overlay images.
    • The number of Nuklear overlay images must match the number of images in your Vulkan swap chain.
    • You must call nk_sdl_resize whenever the overlay images are resized.

    Integration Workflow:

    1. Initialization: Call nk_sdl_init providing your window, device, physical device, graphics queue indices, overlay image views, swap chain length, and format.
    2. Rendering: In your draw loop, call nk_sdl_render to render to the specific image_index. This returns an nk_semaphore.
    3. Synchronization: Use the returned semaphore to coordinate with your application's rendering pipeline. Your application should then sample from the overlay_image to produce the final swap chain image.
    /* Setup */
    struct nk_context *ctx = nk_sdl_init(
        demo.win, 
        demo.device, 
        demo.physical_device, 
        demo.indices.graphics,
        demo.overlay_image_views, 
        demo.swap_chain_images_len,
        demo.swap_chain_image_format, 
        0, // flags
        MAX_VERTEX_BUFFER, 
        MAX_ELEMENT_BUFFER
    );
    
    /* In the draw loop */
    k_semaphore semaphore =
        nk_sdl_render(demo.graphics_queue, image_index,
                        demo.image_available, NK_ANTI_ALIASING_ON);
    
    if (!render(&demo, &bg, nk_semaphore, image_index)) {
        fprintf(stderr, "render failed\n");
        return false;
    }
  8. Integrate Nuklear into your project

    master

    Nuklear is a single-header library that can be used in two modes:

    1. Header-only mode: The default mode. Include nuklear.h in your headers. This mode does not contain the actual implementation.
    2. Implementation mode: Required to actually compile the library. You must define the NK_IMPLEMENTATION macro in exactly one .c or .cpp file before including the header.

    CRITICAL: Every time you include nuklear.h, you must define the exact same optional configuration flags. Failure to do so can lead to compiler errors or stack corruption.

    #define NK_IMPLEMENTATION
    #include "nuklear.h"
  9. Generate the self-contained nuklear_sdl_vulkan.h header

    master

    To create a single-header release for the SDL Vulkan backend, run make within the demo/sdl_vulkan/src directory. This process compiles the necessary SPIR-V shaders and inlines them into a single file named nuklear_sdl_vulkan.h.

    Once generated, copy nuklear_sdl_vulkan.h to the parent directory to complete the release process.

    Prerequisites: You must have the following tools installed on your system:

    • xxd
    • glslc (Vulkan shader compiler)
    • awk
    # Navigate to the source directory
    cd demo/sdl_vulkan/src
    
    # Execute make to generate the header
    make
    
    # Copy the result to the parent directory
    cp nuklear_sdl_vulkan.h ..
  10. Implement soft keyboard support for iOS

    master

    To support soft keyboards on iOS, you must create a UIView subclass that implements the UIKeyInput interface. Because the Allegro keyboard driver does not listen for iOS events, you must use a custom event emitter to pass keyboard events to the backend using: (void)setCustomKeyboardEventSource:(ALLEGRO_EVENT_SOURCE *)ev_src.

    To detect when Nuklear requires the keyboard to open or close, check the flags returned by nk_edit_string against NK_EDIT_ACTIVATED and NK_EDIT_DEACTIVATED.

    k_flags ed_flags = nk_edit_string(ctx, NK_EDIT_FIELD, field_buffer, &field_len, 64, nk_filter_default);
    if (ed_flags & NK_EDIT_ACTIVATED)
        open_ios_soft_keyboard();
    if (ed_flags &  NK_EDIT_DEACTIVATED)
        close_ios_soft_keyboard();