Welcome to the manual for the SMGUI, the state-mode graphical user interface toolkit.
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.
Hint
This manual can be used off-line. From the right-click pop-up menu, choose "Save As".
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 right 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 before ui.h.
1
2
3
4
#include "ui_glfw.h"
#include "ui_psf2.h"
#define UI_IMPLEMENTATION
#include "ui.h"
The reference implementation for backend uses GLFW3, SDL2/3 and X11; for fonts PC-Screen-Font (same that Linux Console uses), and Scalable Screen Font (much more efficient than TTF or OTF, and any font, be it bitmap, vector or pixel font, can be converted into SSFN).
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).
Unless stated otherwise, all functions return a negative error code.
Define | Description |
---|---|
UI_OK | Successful, no error |
UI_ERR_BADINP | Bad input argument |
UI_ERR_BACKEND | Error intializing the backend |
UI_ERR_NOMEM | Memory allocation error |
1
int ui_init(ui_t *ctx, int txtc, char **txtv, int w, int h, ui_image_t *icon);
Initializes the UI context and opens a window. The very first string in the txtv[] string array must be the window's title, the rest is up to you.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
txtc | Number of strings |
txtv | Strings array for localization |
w | Window width |
h | Window height |
icon | Window icon image (or NULL) |
Returns 0 on success, an error code otherwise.
1
int ui_free(ui_t *ctx);
Closes the window and frees internal buffers.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
Returns 0 on success, an error code otherwise.
1
int ui_fullscreen(ui_t *ctx);
Toggle context between fullscreen and windowed mode (after initialization it's windowed).
Parameter | Description |
---|---|
ctx | Pointer to UI context |
Returns 0 on success, an error code otherwise.
1
void *ui_getwindow(ui_t *ctx);
In case there's a need, you can query the underlying backend's window handle with this function.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
Returns an implementation specific handle.
General structure of your program should look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* UI context, one per window */
ui_t ctx;
/* provide a localization strings array */
enum { WINDOW_TITLE, HELLO_WORLD };
char *english[] = { "Window title", "Hello World!" };
/* open a window and initialize the context */
ui_init(&ctx, 2, english, 640, 480, NULL);
/* your main game loop */
do {
/* do your thing, draw what you want to draw */
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* ... */
/* get events and add an UI layer with your desired form on top */
/* if this returns NULL, you should exit */
if(!(evt = ui_event(&ctx, form))) break;
/* parse the events */
switch(evt->type) {
case UI_EVT_KEY: /* key press */ break;
case UI_EVT_MOUSE: /* mouse button event */ break;
case UI_EVT_GAMEPAD: /* gamepad event */ break;
/* ... */
}
} while(1);
/* close the window */
ui_free(&ctx);
SMGUI supports localization, and for that you must gather all the strings in an array. I also suggest to create an enum for the indeces.
char *dictionary[];
You must pass this array when you open the window. The very first string in the array must be the window's title, the rest is up to you.
1
int ui_settxt(ui_t *ctx, char **txtv);
You can change the language any time you want. The new txtv[] array must have exactly the same number of elements as the one you have used for initialization.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
txtv | Strings array |
Returns 0 on success, an error code otherwise.
SMGUI supports any kind of fonts, and ships two reference implementations, ui_psf2.h (default, simple bitmap fonts) and ui_ssfn.h (vector fonts, scaled bitmap and pixelmap fonts), but you can also add your own.
1
int ui_fonthook(ui_t *ctx, ui_font_bbox bbox, ui_font_draw draw);
For a custom font format you'll have to pass two hooks to the context.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
bbox | Bounding box function |
draw | Font renderer function |
Returns 0 on success, an error code otherwise.
The hooks are:
1
2
typedef int (*ui_font_bbox)(void *fnt, char *str, char *end,
int *w, int *h, int *l, int *t);
Measures the string and returns its width in w, height in h in pixels. If there's a left bearing, l is set. Baseline is set from the top in t (both l and t can be NULL if not interested). The string is a zero terminated UTF-8 string in str, but if end is not NULL, then it must stop at end.
1
2
3
typedef int (*ui_font_draw)(void *fnt, char *str, char *end,
uint8_t *dst, uint32_t color, int x, int y, int l, int t, int p,
int cx0, int cy0, int cx1, int cy1);
Renders the string at x, y (with left bearing l and baseline from top t) into a pixel buffer dst which has p bytes in a line (pitch). It is very important that this function must not modify pixels outside of the cx0, cy0 to cx1, cy1 crop region. The implementation specific font is passed in fnt, the font's color in color, the zero terminated UTF-8 string itself in str, but if end is not NULL, then it must stop at end.
Warning
This function does not initialize the font, it just stores the pointer and passes it to the hooks. Font initialization and releasing is platform specific and up to the user (PSF2 needs none).
1
int ui_font(ui_t *ctx, void *fnt);
Sets the current font to be used.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
fnt | Font to be used |
Returns 0 on success, an error code otherwise.
1
int ui_swcursor(ui_t *ctx, ui_image_t *cursor);
Disables hardware mouse cursor and replaces it with an image cursor from software.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
cursor | A cursor image |
Returns 0 on success, an error code otherwise.
1
int ui_hwcursor(ui_t *ctx);
Disables software cursor and enables hardware mouse cursor.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
Returns 0 on success, an error code otherwise.
SMGUI supports color themes, which are basically palettes. Each palette entry corresponds to a specific UI element's color. These are in order:
1
int ui_theme(ui_t *ctx, uint32_t *theme);
Sets a custom theme. The theme[] array has as many elements as UI colors, see the list above.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
theme | Pointer to a palette |
Returns 0 on success, an error code otherwise.
SMGUI supports skins, which are images, each corresponding to a specific part of the UI. These are in order:
1
int ui_skin(ui_t *ctx, ui_image_t *skin);
Sets a custom skin. The skin[] array has as many elements as UI elements, see the list above.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
skin | Array of skin images |
Returns 0 on success, an error code otherwise.
If you include stb_image.h before ui.h, then you can also use the following function to load skin from a single PNG file:
1
int ui_pngskin(ui_t *ctx, uint8_t *png, int size);
Sets a custom skin from a packed PNG file.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
png | Pointer to a PNG image |
size | Size of the PNG image |
Returns 0 on success, an error code otherwise.
The packed PNG must have a comment, with x, y, w, h ASCII decimal numbers in each line that describe areas on the image. To create such packed skin PNGs, you can use for example sprpack with the -cdt flags, like:
sprpack -cdt skin.png mouse.png popup_topleft.png popup_topmiddle.png ...
You must list all separate UI element PNGs and in the exact same order as listed above, otherwise the skin PNG won't work.
1
ui_event_t *ui_event(ui_t *ctx, ui_form_t *form);
This is the heart of SMGUI, this function queries the events and also adds an UI layer on top with the desired form layout to the window.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
form | Form layout |
Returns NULL if the main loop should exit, an event otherwise.
If you change one of the variables that the passed form references, then you must call ui_refresh() before calling ui_event() to force a redrawing.
The returned event should be parsed with a switch on evt->type, because most fields depend on the type.
This is generated whenever a mouse button is pressed or released.
Parameter | Description |
---|---|
evt->type | UI_EVT_MOUSE |
evt->btn | Current button's state |
evt->x | Current mouse X position |
evt->y | Current mouse Y position |
The btn field is a bitfield with the following values:
Define | Description |
---|---|
UI_BTN_L | Left mouse button |
UI_BTN_M | Middle mouse button |
UI_BTN_R | Right mouse button |
UI_BTN_U | Wheel scrolled up |
UI_BTN_D | Wheel scrolled down |
UI_BTN_A | Wheel scrolled left |
UI_BTN_B | Wheel scrolled right |
UI_BTN_RELEASE | set if this is a release event |
If you want to know the mouse's position without an event, then you can use
1
int ui_getmouse(ui_t *ctx, int *x, int *y);
Parameter | Description |
---|---|
ctx | Pointer to UI context |
x | Pointer to store X position |
y | Pointer to store Y position |
Returns 0 on success, error code otherwise.
This is generated whenever the gamepad state changes.
Parameter | Description |
---|---|
evt->type | UI_EVT_GAMEPAD |
evt->btn | Current button's state |
evt->x | Left joystick X position |
evt->y | Left joystick Y position |
evt->rx | Right joystick X position |
evt->ry | Right joystick Y position |
evt->key[0] | Gamepad's index if there's more than one |
Joystick coordinates are signed, and in the range -32768 .. +32768. The btn field is a bitfield with the following values:
Define | Description |
---|---|
UI_BTN_A | The 'A'/cross button state |
UI_BTN_B | The 'B'/circle button state |
UI_BTN_X | The 'X'/square button state |
UI_BTN_Y | The 'Y'/triangle button state |
UI_BTN_L | DPad left button state |
UI_BTN_R | DPad right button state |
UI_BTN_U | DPad up button state |
UI_BTN_D | DPad down button state |
UI_BTN_BA | Back button state |
UI_BTN_ST | Start button state |
UI_BTN_GU | Guide button state |
UI_BTN_LT | Left thumb button state |
UI_BTN_RT | Right thumb button state |
UI_BTN_LS | Left shoulder button state |
UI_BTN_RS | Right shoulder button state |
This is generated whenever a key is pressed on the keyboard.
Parameter | Description |
---|---|
evt->type | UI_EVT_KEY |
evt->btn | Modifier key's state |
evt->key | Pressed key |
The returned key is an UTF-8 character, or (in case of special keys) an invalid UTF-8 sequence with the key's name. To distinguish, check if key[1] is non-zero and the most significant bit in key[0] is set (valid UTF-8) or clear (a key name).
Hint
There's no mapping with magic defines, to figure out what code a certain key generates, just print evt->key.
The btn field is a bitfield with the following values:
Define | Description |
---|---|
UI_BTN_SHIFT | One of the Shift keys is pressed |
UI_BTN_CONTROL | One of the Control keys is pressed |
UI_BTN_ALT | One of the Alt keys is pressed (right key is often called AltGr) |
UI_BTN_GUI | One of the GUI keys is pressed |
For the modifier keys (LShift, RShift, LCtrl, RCtrl, LAlt, RAlt, LGui and RGui) you'll also receive a key release event, with UI_BTN_RELEASE being set. Other keys only generate a key press event.
This is generated whenever a file is dropped on the window.
Parameter | Description |
---|---|
evt->type | UI_EVT_DROP |
evt->x | Current mouse X position |
evt->y | Current mouse Y position |
evt->fn | Path of the file |
This is generated whenever the window is resized.
Parameter | Description |
---|---|
evt->type | UI_EVT_RESIZE |
evt->x | New window width |
evt->y | New window height |
To query the clipboard (after you have received a keyboard event with Paste key), call
1
char *ui_getclipboard(ui_t *ctx);
Parameter | Description |
---|---|
ctx | Pointer to UI context |
Returns NULL if the clipboard is empty, otherwise the content in a newly allocated buffer. It is the caller's duty to free this buffer after finished using it.
To set the clipboard (after you have received Cut or Copy key), call
1
char *ui_setclipboard(ui_t *ctx, char *str);
Parameter | Description |
---|---|
ctx | Pointer to UI context |
str | String to copy to clipboard |
Returns 0 on success, error code otherwise.
The SMGUI is not an immediate-mode GUI, but neither uses callbacks. Instead it expects that you already have your variables, and you provide a form which references those variables.
The form is an array of ui_form_t elements, and the last element's type MUST be UI_END. Some of the fields are common, others are type specific. The common fields are as follows:
Parameter | Description |
---|---|
form->ptr | Pointer to data |
form->type | Form element type |
form->align | Form element alignment |
form->flags | Form element flags (like visibility) |
form->x | Form element desired position |
form->y | Form element desired position |
form->w | Form element desired width |
form->h | Form element desired height |
form->m | Form element margin |
form->p | Form element padding (containers only) |
These control how a certain field is displayed.
SMGUI does not use the classic packed rows / columns / grid layout, instead it utilizes a HTML-like flexible flow. You can specify coordinates in form->x and form->y three different ways: relative, absolute and percentage.
If you leave form->w and form->h as 0, then the element's width and height will be automatically calculated. If UI_ABS() macro is used on them, then the parent container's width (or height) minus the value will be the width (or height).
You can also use form->align to specify alignment on the given x, y coordinates. This is an OR'd bitmask.
Examples:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ui_form_t form[] = {
/* these will be placed one after another, left to right,
* break to the next line if necessary, tightly packed */
{ .type = UI_LABEL, .label = 1 },
{ .type = UI_LABEL, .label = 1 },
/* this will also be placed after the other but with a spacing */
{ .type = UI_LABEL, .x = UI_REL(10), .label = 1 },
/* this will be placed at absolute position */
{ .type = UI_LABEL, .x = UI_ABS(100), .y = UI_ABS(100), .label = 1 },
/* this will be placed at the centre of the window */
{ .type = UI_LABEL, .align = UI_CENTER | UI_MIDDLE,
.x = UI_PERCENT(50), .y = UI_PERCENT(50), .label = 1 },
/* this will be screen width - 20 and screen height - 20 in size */
{ .type = UI_POPUP, .x = UI_ABS(10), .y = UI_ABS(10),
.w = UI_ABS(20), .h = UI_ABS(20), .ptr = &popupform },
/* it is important to close the list */
{ .type = UI_END }
};
Since the form just references your variables, it is perfectly fine if you have a thread that displays the layout and you handle your variables from another thread, this will just work. On the other hand if you wish to dynamically change your form from another thread, then it is your job to properly protect your form with semaphores. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ui_form_t form[];
/* this function can be called from any thread */
void regenerate_form()
{
mutex_lock(&my_form_mutex);
/* do changes to the form[] array here */
mutex_unlock(&my_form_mutex);
}
/* and in the main thread in your main loop */
mutex_lock(&my_form_mutex);
evt = ui_event(&ctx, form);
mutex_unlock(&my_form_mutex);
Note that since SMGUI itself does not use any threading, therefore you can use whatever threading and mutex implementation you want to use. The SDL backend for example provides SDL_Mutex, and for GLFW one could use the pthread library.
Redrawing the form happens automatically in event handling when needed, and that's it. However if you change one of the referenced variables outside of the UI's scope, then you must call
1
int ui_refresh(ui_t *ctx);
Informs UI that it must redraw the UI layer.
Parameter | Description |
---|---|
ctx | Pointer to UI context |
Returns 0 on success, error code otherwise.
If a container is preceeded by a toggle field, then that toggle controls the visibility of that container.
Draws a popup.
Parameter | Description |
---|---|
form->type | UI_POPUP |
form->flags | Might want UI_HIDDEN, UI_DRAGGABLE or UI_RESIZABLE |
form->ptr | Pointer to another ui_form_t buffer |
form->m | Margin in pixels |
form->p | Padding in pixels |
form->label | Title, index to the localized strings array (or 0) |
Same as popup, but by default its state is UI_HIDDEN, and there can be only one menu visible at any time. Its checkbox and radiobutton children are highlighted when you hover the mouse over them.
Parameter | Description |
---|---|
form->type | UI_MENU |
form->flags | Might want UI_SCROLL |
form->ptr | Pointer to another ui_form_t buffer |
form->m | Margin in pixels |
form->p | Padding in pixels |
Does not draw anything, just provides a way to group further fields, hide / show and position them together.
Parameter | Description |
---|---|
form->type | UI_DIV |
form->flags | Might want UI_SCROLL |
form->ptr | Pointer to another ui_form_t buffer |
form->m | Margin in pixels |
form->p | Padding in pixels |
Displays fields as a table or grid. Requires including the ui_table.h module.
Parameter | Description |
---|---|
form->type | UI_TABLE |
form->flags | Might want UI_SCROLL or UI_NOHEADER |
form->ptr | Pointer to data records |
form->tblsel | Index of the selected data record |
form->tblnum | Number of data records |
form->tblsiz | Size of one data record in bytes |
form->tblrow | Row size in pixels |
form->tblcol | Column size in pixels (if grid, otherwise 0) |
form->data | Pointer to ui_form_t headers |
form->cmps | Comparators for sorting (or NULL) |
form->m | Cellmargin in pixels |
The form->ptr points to the data records, which in case of a table is probably an array of structs. The form->data list must have at least one UI field and MUST always end in an UI_END field. This contains the hdr headers and also specifies how to display a certain column. For a table, set form->tblcol to 0, and you probably want multiple headers in form->data. For a grid, set form->tblcol to non-zero and then only the first header used in form->data for all cells.
Parameter | Description |
---|---|
hdr->type | The cell's display type |
hdr->tblhdr | Header title, index to the localized strings array |
hdr->tblofs | Offset of field within the data record (table only) |
hdr->flags | Set UI_POINTER if this column's field is a pointer |
hdr->w | Column width, might use UI_PERCENT |
To enable sorting, you must provide twice as many comparator functions as header fields. First is for ascending, the second is for descending order per column.
1
typedef int (*ui_comp)(const void *a, const void *b);
The prototype is a standard POSIX comparator, used as libc qsort() function's parameter.
Draws a text label. If form->label is 0, then form->ptr should point to a zero terminated UTF-8 string.
Parameter | Description |
---|---|
form->type | UI_LABEL |
form->label | Index to the localized strings array (or 0) |
form->ptr | Only if label is 0, pointer to a string |
Draws a text label. Same as UI_LABEL, but instead of form->label uses the form->desc field of the element the mouse is hovering over. If this is 0, then form->ptr should point to a zero terminated UTF-8 string.
Parameter | Description |
---|---|
form->type | UI_STATUS |
form->ptr | Only if hover->desc is 0, pointer to a string |
hover->desc | Index to the localized strings array (or 0) |
Draws an integer as a decimal number label. The number in the define specifies how many bits used to store the variable.
Parameter | Description |
---|---|
form->type | UI_DEC8 / UI_DEC16 / UI_DEC32 / UI_DEC64 |
form->ptr | Pointer to the value |
Draws an integer as a hexadecimal number label. The number in the define specifies how many bits used to store the variable.
Parameter | Description |
---|---|
form->type | UI_HEX8 / UI_HEX16 / UI_HEX32 / UI_HEX64 |
form->ptr | Pointer to the value |
Draws an integer (64 bit) as a progressbar.
Parameter | Description |
---|---|
form->type | UI_PBAR |
form->ptr | Pointer to the int64 value |
form->max | Total value |
Draws a floating point number label.
Parameter | Description |
---|---|
form->type | UI_DEC_FLOAT |
form->ptr | Pointer to the value |
This field draws an image icon. If form->ptr is not NULL, then also clickable and acts as a button.
Parameter | Description |
---|---|
form->type | UI_IMAGE |
form->icon | Pointer to an ui_image_t struct |
form->ptr | Pointer to the int value (or NULL) |
form->value | Int value for this label |
The ui_image_t image structure looks like:
Field | Description |
---|---|
w | Width in pixels |
h | Height in pixels |
p | Pitch in bytes (bytes in a row, at least w * 4) |
buf | Pixel buffer with 32-bit RGBA packed pixels |
Draws a text input box. The buffer should store an editable UTF-8 string. Normally it accepts all keys as input except control characters, but you can further filter the keys: UI_FILTER_ID (a UNIX id), UI_FILTER_VAR (a variable name, same as UNIX id but first character can't be 0-9), UI_FILTER_EXPR (an expression, same as variable plus parenthesis and operators), and UI_FILTER_HEX (only allows hexadecimal 0-9A-F keys). The UI_FILTER_PASS filters not the input, but the output, displays asterisks. Copy'n'paste works too. If the ui_textosk.h module is included, then on input an on-screen keyboard is displayed from software, otherwise OS-native OSK is only shown if the platform supports it.
Parameter | Description |
---|---|
form->type | UI_TEXT |
form->ptr | Pointer to a buffer |
form->max | Size of the buffer |
form->inc | 0 for all, or a UI_FILTER_x key filter |
Draws a selectbox. When the users clicks on it, opens a selection popup.
Parameter | Description |
---|---|
form->type | UI_SELECT |
form->ptr | Pointer to an int value |
form->optc | Maximum value plus 1, number of options |
form->optv | Pointer to a string array, options |
form->m | Right margin |
Draws an option selector. Same as a selectbox, just with a number input box visual, no popup.
Parameter | Description |
---|---|
form->type | UI_OPTION |
form->ptr | Pointer to an int value |
form->optc | Maximum value plus 1, number of options |
form->optv | Pointer to a string array, options |
form->m | Left and right margin |
Draws a floating point number input box.
Parameter | Description |
---|---|
form->type | UI_FLOAT |
form->ptr | Pointer to the value |
form->fmin | Minimum value |
form->fmax | Maximum value |
form->finc | Increment value |
form->m | Left and right margin |
Draws an integer input box. The number in the define specifies how many bits used to store the variable.
Parameter | Description |
---|---|
form->type | UI_INT8 / UI_INT16 / UI_INT32 / UI_INT64 |
form->ptr | Pointer to the value |
form->min | Minimum value |
form->max | Maximum value |
form->inc | Increment value |
form->m | Left and right margin |
Draws an integer slider box (32 bit only).
Parameter | Description |
---|---|
form->type | UI_SLIDER |
form->ptr | Pointer to the int value |
form->min | Minimum value |
form->max | Maximum value |
form->inc | Increment value |
Draws an integer as a vertical scroll bar (32 bit only). The containers have their own scrollbars, so this is only if you want to use for whatever other reason.
Parameter | Description |
---|---|
form->type | UI_VSCRBAR |
form->ptr | Pointer to the int value |
form->max | Maximum value |
Draws an integer as a horizontal scroll bar (32 bit only).
Parameter | Description |
---|---|
form->type | UI_HSCRBAR |
form->ptr | Pointer to the int value |
form->max | Maximum value |
Draws an integer as color (32 bit only). When the users clicks on it, opens a color picker.
Parameter | Description |
---|---|
form->type | UI_COLOR |
form->ptr | Pointer to the int value |
Draws a text input box. The buffer should store an editable UTF-8 string, a file path. When the user clicks on it, opens a path picker, which can be used to easily edit the string. See the description of the str parameter there. Requires including the ui_file.h module.
Parameter | Description |
---|---|
form->type | UI_FILE |
form->ptr | Pointer to a buffer with path |
form->max | Size of the buffer |
form->min | Minimum height of the path picker |
form->str | Index to the localized strings array (or 0) |
Draws a path picker. The str parameter is the first index in the localized strings array of 8 strings, which must contain: file name, size, modification date/time, just now, N minutes ago, an hour ago, N hours ago, yesterday. Requires including the ui_file.h module.
Parameter | Description |
---|---|
form->type | UI_PATH |
form->ptr | Pointer to a buffer with path |
form->max | Size of the buffer |
form->str | Index to the localized strings array (or 0) |
form->data | Pointer to an ui_path_t context |
The ui_path_t structure looks like:
Field | Description |
---|---|
flags | Path flags |
exts | Extension list (or NULL) |
select | Selection okay callback (or NULL) |
The flags are:
Define | Description |
---|---|
UI_PATH_SEARCH | Display a search field too |
UI_PATH_NEWDIR | Display an add new directory button |
UI_PATH_ONLYDIR | Only list directories |
UI_PATH_HIDDEN | Display hidden files too |
The extension list is a zero terminated string list, and the whole string is terminated by two null bytes, for example png\0jpg\0\0. If given, then only files with extensions listed here are shown.
If the select callback is NULL, then simply sub-directories are entered, and files are returned. If it's given with
1
typedef int (*ui_form_select)(char *path, int isdir);
then it's supposed to return 1 if the selected path should be returned, or 0 if not. If 0 returned, then sub-directories are entered and nothing happens with files. The path argument ends in a directory separator if it's a directory and then isdir is 1. This can be used to add a further criteria to selecting a path, like a directory containing a certain file or a file starting with a certain magic or not. The callback might implement whatever additional checks it wants.
This is a special field, should be followed by a container field, and it controls that container's visibility. If by any chance the next field isn't a container, then form->value is an index to the form passed to ui_event(). If form->label is 0, then form->ptr should point to a zero terminated UTF-8 string, displayed as label. Normally before that there's a right or down triangle, but with UI_NOBULLET there's no triangle rather the label is displayed with a different color (see color theme, this is to provide menu toggles).
Parameter | Description |
---|---|
form->type | UI_TOGGLE |
form->flags | Might want UI_NOBULLET |
form->label | Index to the localized strings array (or 0) |
form->ptr | Only if label is 0, pointer to a string |
form->value | Only if next field isn't a container, ui_event() form's index |
Draws a checkbox with label. The pointed value tells if it's checked. When clicked, int value is XOR'd at that address.
Parameter | Description |
---|---|
form->type | UI_CHECK |
form->flags | Might want UI_NOBULLET |
form->ptr | Pointer to the int value |
form->value | Bitmask for this button |
form->label | Index to the localized strings array |
Draws a radiobutton with label. The pointed value tells if it's checked. When clicked, int value is stored at that address.
Parameter | Description |
---|---|
form->type | UI_RADIO |
form->flags | Might want UI_NOBULLET |
form->ptr | Pointer to the int value |
form->value | Int value for this button |
form->label | Index to the localized strings array |
Draws a button. The pointed value tells if it's pressed. When clicked, int value is stored at that address. Might have an icon, a localized string, or both. When using skins, buttons might have a shadow which misaligns the button title vertically. If that happens, set the top margin in m (which could be negative as well).
Parameter | Description |
---|---|
form->type | UI_BUTTON |
form->flags | Might want UI_NOBORDER |
form->ptr | Pointer to the int value |
form->value | Int value for this button |
form->label | Index to the localized strings array |
form->icon | Pointer to an ui_image_t struct |
form->m | Top margin if skinned |
Draws a button which acts like a toggle. When clicked, toggles the pointed container field's visibility. If ptr is NULL, then form->value is an index to the form passed to ui_event().
Parameter | Description |
---|---|
form->type | UI_BTNTGL |
form->flags | Might want UI_NOBORDER |
form->ptr | Pointer to an ui_form_t container field |
form->value | Only if ptr is NULL, ui_event() form's index |
form->label | Index to the localized strings array |
form->icon | Pointer to an ui_image_t struct |
form->m | Top margin if skinned |
Draws an icon if value is checked, otherwise nothing. When clicked, int value is XOR'd at that address. Acts as a checkbox.
Parameter | Description |
---|---|
form->type | UI_BTNICN |
form->ptr | Pointer to the int value |
form->value | Bitmask for this button |
form->icon | Pointer to an ui_image_t struct |
Draws a series of anti-aliased lines. form->ptr must point to an array of int16_t (short int) values, multiple x and y pairs, and the last pair must be 0,0.
Parameter | Description |
---|---|
form->type | UI_LINES |
form->ptr | Pointer to the int16_t array |
form->value | 32 bit RGBA color |
Connects two points horizontally with a curve. form->ptr must point to an array of exactly 4 int16_t values, x0 and y0 starting point, x1 and y1 end point pair.
Parameter | Description |
---|---|
form->type | UI_HCONNECT |
form->ptr | Pointer to int16_t array with 4 elements |
form->value | 32 bit RGBA color |
Connects two points vertically with a curve. form->ptr must point to an array of exactly 4 int16_t values, x0 and y0 starting point, x1 and y1 end point pair.
Parameter | Description |
---|---|
form->type | UI_VCONNECT |
form->ptr | Pointer to int16_t array with 4 elements |
form->value | 32 bit RGBA color |
Draws an arbitrary Bezier curve. form->ptr must point to an array of exactly 8 int16_t values, x0 and y0 starting point, x1 and y1 end point, cx0 and cy0 first control point, cx1 and cy1 second control point's coordinate pair.
Parameter | Description |
---|---|
form->type | UI_CURVE |
form->ptr | Pointer to int16_t array with 8 elements |
form->value | 32 bit RGBA color |
User specified custom widgets can be added as well, it only needs 4 hooks per kind.
Parameter | Description |
---|---|
form->type | UI_CUSTOM |
form->flags | You must respect UI_HIDDEN and UI_DISABLED |
form->ptr | Pointer to an arbitrary data buffer |
form->data | Pointer to an arbitrary data context |
form->str | Index to the localized strings array (or 0) |
form->bbox | Pointer to a bounding box callback |
form->view | Pointer to a renderer callback |
form->ctrl | Pointer to an event handler callback |
form->fini | Pointer to an optional finish callback |
1
2
typedef int (*ui_bbox)(ui_t *ctx, int x, int y, int w, int h,
ui_form_t *form, int *dw, int *dh);
This function receives the calculated area x, y, w, h where the widget should be located, the form element in form, and must return the destination width in dw and destination height in dh.
1
2
typedef int (*ui_view)(ui_t *ctx, int x, int y, int w, int h,
ui_form_t *form);
This function receives the effective area x, y, w, h where the widget is located, the form element in form, and should render the widget there. It might use the low level functions like _ui_line(), _ui_cbez(), _ui_rect(), _ui_blit() etc., or might directly set pixels on the ctx->screen image. It is very important that it must not draw outside of the designated area.
1
2
typedef int (*ui_ctrl)(ui_t *ctx, int x, int y, int w, int h,
ui_form_t *form, ui_event_t *evt);
This function receives the effective area x, y, w, h where the widget is located, the form element in form, and the current event in evt for event handling. This hook is only called if the form isn't UI_HIDDEN nor UI_DISABLED, and the mouse is over the widget.
1
typedef int (*ui_fini)(ui_t *ctx, ui_form_t *form);
This optional hook is needed if the widget allocates extra memory. It is expected to be called multiple times, so it must not double free those buffers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#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[] = {
{ .type = UI_BUTTON, .flags = UI_FORCEBR,
.ptr = &button, .value = 1,
.label = BUTTON_TITLE },
{ .type = UI_RADIO, .flags = UI_NOBR,
.ptr = &difficulty, .value = 0,
.y = 5, .label = EASY_TITLE },
{ .type = UI_RADIO, .flags = UI_FORCEBR,
.ptr = &difficulty, .value = 1,
.x = 20, .label = HARD_TITLE },
{ .type = UI_LABEL, .flags = UI_NOBR,
.y = 5, .label = VOLUME_TITLE },
{ .type = UI_SLIDER, .ptr = &volume, .max = 100 },
{ .type = UI_END }
};
ui_form_t form[] = {
{ .type = UI_POPUP, .align = UI_CENTER | UI_MIDDLE,
.ptr = &popup,
.x = UI_PERCENT(50), .y = UI_PERCENT(50),
.m = 10, .label = POPUP_TITLE },
{ .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;
}
You can tweak SMGUI by providing some define before you include ui.h.
Warning
Only define this after you have included the modules.
Set this if you want to include not just the definitions but the implementation too.
Disable anti-aliased lines (eliminates math.h and libm dependency).
Set the maximum number of pending events. If not defined otherwise, defaults to 16.
Set the maximum number of visible popups. If not defined otherwise, defaults to 16.
Set if you want to call glfwInit() / glfwTerminate(), SDL_Init() / SDL_Quit() etc. yourself. This is needed if you want SMGUI to handle multiple windows.
Set if you don't want ui_event() to flush the window, and you call glfwSwapBuffers() / SDL_RenderPresent() / etc. yourself. This is needed if you want to draw over the UI layer.
Separately you can tweak the GLFW3 backend by providing some define before you include ui_glfw.h.
By default, this backend is compiled with shaders for OpenGL3.3 core profile with both glad and glew extension loader support.
Use old-school OpenGL API. This works everywhere and requires no extension loader nor shaders. The downside is, that some video card drivers are buggy and break backward compatibility, so you won't be able to use your own shaders either with this option.
Use shaders, but only OpenGL ES 2.0 (mobile platform variant). For supporting legacy mobiles only, most modern devices have no issues using OpenGL 3.3 core.
If you want to use your own GLFW3 hooks, then define this, and also call the backend's hooks from your hooks. For example:
1
2
3
4
5
6
7
8
9
10
11
/* set your hook as usual */
glfwSetMouseButtonCallback(ui_getwindow(ctx), my_glfw_mouse);
/* your hook */
void my_glfw_mouse(GLFWwindow *window, int button, int action, int mods)
{
/* parse what you want */
/* ... */
/* at the end also call the ui_glfw's hook */
ui_glfw_mouse(window, button, action, mods);
}
Licensed under the terms of the permissive MIT license.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.