State-mode GUI, single header ANSI C library

bzt 2d5023a029 Allow to specify font on command line in widgets.c 2 months ago
docs cb058bc8b3 More docs 2 months ago
examples 2d5023a029 Allow to specify font on command line in widgets.c 2 months ago
mods 4039219b77 Minor fixes in ui_path 3 months ago
public cb058bc8b3 More docs 2 months ago
.gitignore 82a599d678 Table, grid, button icon and minor fixes 4 months ago
.gitlab-ci.yml 9c0432aff4 Initial commit 4 months ago
LICENSE 9c0432aff4 Initial commit 4 months ago
README.md bef60c7050 Replaced UI_HEADERONLY with UI_IMPLEMENTATION 3 months ago
ui.h 8569205c90 Make sure disabled button not drawn as pressed 3 months ago

README.md

State Mode Graphical User Interface

The SMGUI is minimal-state graphical user interface toolkit written in ANSI C, which is neither callback-driven nor immediate-mode, rather state-mode. I've created it because I've tried Nuklear and I was extremely disappointed with it (huge, inefficient and extremely hard to integrate).

The main concept of a state-mode UI is, that you already have your variables, so you reference those from a layout, which has no callbacks neither requires immediate-mode calls, it is just uses those already existing variables for rendering the GUI states.

Features

  • State-mode graphical interface toolkit (no immediate-mode, no callbacks)
  • Single-header library (with optional modules)
  • Customizable modules (you include what you need)
  • Written in ANSI C (really, unlike Nuklear, this truly compiles with gcc -ansi)
  • Small codebase (really small, just 3k SLoC)
  • Focus on portability (backend and OS agnostic, comes with GLFW3, SDL2/3 and X11 bindings)
  • Focus on efficiency (truly)
  • And focus on simplicity (integrate with just 5 lines of code)
  • No dependencies in the base header (just libc)
  • Fully skinnable and customizable
  • Low memory footprint (really, unlike that liar Nuklear)
  • Expandable with custom widgets from your code
  • UTF-8 support
  • Multilanguage support (you can change the language even on-the-fly)
  • Multithreading support (you don't have to handle events in your main loop thread like with immediate-mode)
  • No global or hidden state
  • No font baker (which kills UNICODE support entirely, 0x10ffff codepoint is just fucking too much for baking)
  • Font ligatures and kerning supported too if needed (both impossible with baking)
  • Documentation

Building

This library is self-contained in one single header file and can be used either in header-only mode or in implementation mode. The header-only mode is used by default when included and allows including this header in other headers and does not contain the actual implementation. The implementation mode requires defining the preprocessor macro UI_IMPLEMENTATION in exactly one .c/.cpp file before including this file.

The base library is entirely platform and backend agnostic. You can select which backend and font driver module to use just by including them before ui.h.

#include "ui_glfw.h"
#include "ui_psf2.h"
#define UI_IMPLEMENTATION
#include "ui.h"

By default, if no other modules included beforehand, then ui.h includes the GLFW3 backend and PSF2 fonts and also embeds a minimal ASCII-only font (2080 bytes compiled).

Screenshots

Colors

No borders

Skin

Color picker

OSK

File picker

Table and grid

Example

NOTE this is the entire, fully working example, no hidden integration requirements!

#define UI_IMPLEMENTATION
#include <ui.h>

int main(int argc, char **argv)
{
    /* localized strings array */
    enum { WINDOW_TITLE, POPUP_TITLE, BUTTON_TITLE, EASY_TITLE, HARD_TITLE, VOLUME_TITLE };
    char *lang[] = { "Basic demo", "Show", "Button", "easy", "hard", "Volume:" };

    /* variables to store game states */
    int button = 0, difficulty = 0, volume = 25;

    /* form referencing those variables, you use a HTML flow like layout */
    ui_t ctx;
    ui_form_t popup[] = {
        { .ptr = &button,     .value = 1, .type = UI_BUTTON, .flags = UI_FORCEBR,
            .label = BUTTON_TITLE },
        { .ptr = &difficulty, .value = 0, .type = UI_RADIO, .flags = UI_NOBR, .y = 5,
            .label = EASY_TITLE },
        { .ptr = &difficulty, .value = 1, .type = UI_RADIO, .flags = UI_FORCEBR, .x = 20,
            .label = HARD_TITLE },
        { .label = VOLUME_TITLE, .type = UI_LABEL, .flags = UI_NOBR, .y = 5 },
        { .ptr = &volume, .min = 0, .max = 100, .type = UI_SLIDER },
        { .type = UI_END }
    };
    ui_form_t form[] = {
        { .ptr = &popup, .type = UI_POPUP, .m = 10, .label = POPUP_TITLE,
            .x = UI_PERCENT(50), .y = UI_PERCENT(50), .align = UI_CENTER | UI_MIDDLE },
        { .type = UI_END }
    };

    /* initialize the UI context */
    ui_init(&ctx, sizeof(lang)/sizeof(lang[0]), lang, 640, 480, NULL);

    /* wait until user closes the window */
    while(ui_event(&ctx, form)) {
        /* handle button, you can do this from another thread if you want */
        if(button) {
            printf("button clicked\n");
            button = 0;
            ui_refresh(&ctx);
        }
    }

    /* destroy window, free resources */
    ui_free(&ctx);

    return 0;
}

Example

License

Licensed under the terms of the permissive MIT license, see LICENSE.

Cheers, bzt