Nuklear

Small, Immediate Mode, C

https://github.com/vurtun/nuklear

Renderer Not included, you can add it or copy from examples (GLFW3, GDI2, SDL, X11, etc.)

Tech stack used for building a showcase app:

Build system CMake
Languages C
Libraries GLFW
Editor CLion

Internals

Another immediate mode GUI. Single-file dependency (On macOS it requires installing glfw, glew to compile), plain C, OpenGL.

At first glance it looks like everything that you can expect from it: the rendering is blurry, no accessiblity bridge, no OS-awareness, and like this resizing artifact:

nuklear example widget layout problems with window resizing

But this is just one example app, the other one from repository (extended.c) looks better, although, text rendering is still off.

Extended example of Nuklear

In real life, some issues can be mitigated if you build a very complex app and able to invest into it.

Using a wrapper also nice if you want to write a lot of code, and there are plenty of it, e.g. Rust, Go, Python.

Nuklear, along with IMGUI is an example of Immediate Mode GUI. So the basic idea looks like this:

  while(true) {
    // draw a frame
    // react on input
  }

Or to be more specific:


#include "nuklear.h"
#include "nuklear_glfw_gl3.h"


// ..
// Event loop
while (!glfwWindowShouldClose(win)) {
  if (nk_begin(ctx, "Demo", nk_rect(50, 50, 230, 250),
                         NK_WINDOW_BORDER |
                         NK_WINDOW_MOVABLE |
                         NK_WINDOW_SCALABLE |
                         NK_WINDOW_MINIMIZABLE |
                         NK_WINDOW_TITLE)) {
    // ..
    nk_layout_row_static(ctx, 30, 80, 1);
    if (nk_button_label(ctx, "button"))
        fprintf(stdout, "button pressed\n");
    }
    // ..
  }
}

GLFW is an Open Source, multi-platform library for OpenGL, OpenGL ES and Vulkan development on the desktop. It provides a simple API for creating windows, contexts and surfaces, receiving input and events. GLFW is written in C and has native support for Windows, macOS and many Unix-like systems using the X Window System, such as Linux and FreeBSD.

nuklide.h consists of a bunch of nk_ functions that “draw” something on the screen and handle user input each frame using the backend you provided. In this case it’s glfw3

What does nk_button_label do? It’s an alias:

int nk_button_label(struct nk_context *ctx, const char *title)
{
    return nk_button_text(ctx, title, nk_strlen(title));
}

…which could be roughly transformed into the following:


    // ..
    /* calculate button content space */
    content->x = r.x + style->padding.x + style->border + style->rounding;
    content->y = r.y + style->padding.y + style->border + style->rounding;
    content->w = r.w - (2 * style->padding.x + style->border + style->rounding*2);
    content->h = r.h - (2 * style->padding.y + style->border + style->rounding*2);

    /* execute button behavior */
    bounds.x = r.x - style->touch_padding.x;
    bounds.y = r.y - style->touch_padding.y;
    bounds.w = r.w + 2 * style->touch_padding.x;
    bounds.h = r.h + 2 * style->touch_padding.y;

     if (nk_input_is_mouse_hovering_rect(i, r)) {
        *state = NK_WIDGET_STATE_HOVERED;
        if (nk_input_is_mouse_down(i, NK_BUTTON_LEFT))
            *state = NK_WIDGET_STATE_ACTIVE;
        if (nk_input_has_mouse_click_in_rect(i, NK_BUTTON_LEFT, r)) {
            ret = (behavior != NK_BUTTON_DEFAULT) ?
                nk_input_is_mouse_down(i, NK_BUTTON_LEFT):
                #ifdef NK_BUTTON_TRIGGER_ON_RELEASE
                nk_input_is_mouse_released(i, NK_BUTTON_LEFT);
                #else
                nk_input_is_mouse_pressed(i, NK_BUTTON_LEFT);
                #endif
    // ..
    if (style->draw_begin) style->draw_begin(out, style->userdata);
    nk_draw_button_text(out, &bounds, &content, state, style, string, len, align, font);
    if (style->draw_end) style->draw_end(out, style->userdata);

    // ..

So far we only see library-specific code, but Nuklear has a lot of backends, and to add one you need to implement a few functions how to draw geometry on the screen, how handle input devices such as mouses. This is how glfw OpenGL3 specific code looks like:

// ..
/* iterate over and execute each draw command */
 nk_draw_foreach(cmd, &glfw.ctx, &dev->cmds)
 {
   if (!cmd->elem_count)
     continue;
   glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
   glScissor(
       (GLint)(cmd->clip_rect.x * glfw.fb_scale.x),
       (GLint)((glfw.height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h)) * glfw.fb_scale.y),
       (GLint)(cmd->clip_rect.w * glfw.fb_scale.x),
       (GLint)(cmd->clip_rect.h * glfw.fb_scale.y));
   glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
   offset += cmd->elem_count;
 }

Developer experience

It compiles damn fast. But C is unsafe language. There are options.

Accessiblity

mwcampbell wrote

Technical follow-up: It seems to me that implementing today’s platform accessibility APIs (e.g. UI Automation for Windows, AT-SPI for Unix) would be difficult if not impractical for an immediate-mode toolkit like this one. These accessibility APIs expose a tree of UI objects, which the client (e.g. screen reader) can freely navigate and query. This basically assumes that there’s a tree of UI objects in memory, which is the case for all mainstream toolkits as far as I know. But that’s not the case for an immediate-mode toolkit. At least with Nuklear, the content and state of the UI (e.g. the label and current checked state of a checkbox) aren’t stored anywhere in the toolkit’s data structures. So I guess applications would have to play a very active role in implementing accessibility APIs, much more than they would with, say, Qt or even Win32.

Could we though back the tree itself?

OS Integration

So we need to impelement deep linking.

https://github.com/jimon/osx_app_in_plain_c/blob/master/main.c

Other Issues