layout.md 6.0 KB

Layout

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)

Field Flags

These control how a certain field is displayed.

  • UI_HIDDEN not shown, does not influence normal flow
  • UI_NOBULLET for [toggle]s, [checkbox]es and [radiobutton]s only display the label
  • UI_NOHEADER for [table]s and grids, do not show the header
  • UI_NOBR forces the next field in the same line, no line break
  • UI_FORCEBR opposite, breaks flow and forces the next field into a new line
  • UI_ALTSKIN for fields which support multiple skins (eg. tab instead of menu)
  • UI_NOBORDER for containers, do not display the borders
  • UI_NOSHADOW for popups, do not display shadows
  • UI_HSCROLL for containers, display horizontal scrollbar
  • UI_VSCROLL for containers, display vertical scrollbar
  • UI_SCROLL same as UI_HSCROLL + UI_VSCROLL
  • UI_DRAGGABLE for popups, allows the user to move them around
  • UI_RESIZABLE for popups, allows the user to resize them
  • UI_SELECTED make the input box selected
  • UI_DISABLED make the field inactive, different style and non-clickable

Positioning

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.

  • Relative positioning is the default, but for convenience you can also use the UI_REL() macro. A field positioned like this affects the normal flow.
  • For absolute positioning, use the UI_ABS() macro. You can also change the gravity and use UI_ABS_RIGHT() or UI_ABS_BOTTOM() to specify the position relative to the parent container's width or height. Outside of normal flow.
  • For percentage, use the UI_PERCENT() macro. This calculates the position as a parcentage to the parent container's width or height. Outside of normal flow.
  • For percentage plus some pixels, use the UI_PERPLUS() macro.

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.

  • UI_LEFT places the element as form->x is on the left (default)
  • UI_RIGHT places the element as form->x is on the right
  • UI_CENTER places the element so that form->x will be in the middle
  • UI_TOP places the element as form->y will be at the top (default)
  • UI_BOTTOM places the element as form->y will be at the bottom
  • UI_MIDDLE places the element so that form->y will be in the middle

Examples:

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 }
};

Multithreading

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:

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

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

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.