The overall structure when using the library is as follows:
initialise `mu_Context`
main loop:
call `mu_input_...` functions
call `mu_begin()`
process ui
call `mu_end()`
iterate commands using `mu_command_next()`
Before use a mu_Context
should be initialised:
mu_Context *ctx = malloc(sizeof(mu_Context));
mu_init(ctx);
For font alignment and clipping to work correctly you should also set the
context's text_width
and text_height
callback functions:
ctx->text_width = text_width;
ctx->text_height = text_height;
In your main loop you should first pass user input to microui using the
mu_input_...
functions. It is safe to call the input functions multiple
times if the same input event occurs in a single frame.
After handling the input the mu_begin()
function must be called before
processing your UI:
mu_begin(ctx);
Before any controls can be used we must begin a window using one of the
mu_begin_window...
or mu_begin_popup...
functions. The mu_Container
for the window is expected to be either zeroed memory in which case
it will be initialised automatically when used, or to have been
initialised manually using the mu_init_window()
function; once used,
the mu_Container
's memory must remain valid until mu_end()
is called
at the end of the frame. The mu_begin_...
window functions return a
truthy value if the window is open, if this is not the case we should
not process the window any further. When we are finished processing the
window's ui the mu_end_...
window function should be called.
static mu_Container window;
if (mu_begin_window(ctx, &window, "My Window")) {
/* process ui here... */
mu_end_window(ctx);
}
It is safe to nest mu_begin_window()
calls, this can be useful for
things like context menus; the windows will still render separate from
one another like normal.
While inside a window block we can safely process controls. Controls
that allow user interaction return a bitset of MU_RES_...
values. Some
controls — such as buttons — can only potentially return a single
MU_RES_...
, thus their return value can be treated as a boolean:
if (mu_button(ctx, "My Button")) {
printf("'My Button' was pressed\n");
}
The library generates unique IDs for controls internally to keep track
of which are focused, hovered, etc. These are generated either from the
pointer passed to the function (eg. for treenodes, checkboxes, textboxes
and sliders), or the string/icon passed to the function (eg. buttons). An
issue arises then if you have several buttons in a window or panel that
use the same label. The mu_push_id()
and mu_pop_id()
functions are
provided for such situations, allowing you to push additional data that
will be mixed into the unique ID:
for (int i = 0; i < 10; i++) {
mu_push_id(ctx, &i, sizeof(i));
if (mu_button(ctx, "x")) {
printf("Pressed button %d\n", i);
}
mu_pop_id(ctx);
}
When we're finished processing the UI for this frame the mu_end()
function should be called:
mu_end(ctx);
When we're ready to draw the UI the mu_next_command()
can be used
to iterate the resultant commands. The function expects a mu_Command
pointer initialised to NULL
. It is safe to iterate through the commands
list any number of times:
mu_Command *cmd = NULL;
while (mu_next_command(ctx, &cmd)) {
if (cmd->type == MU_COMMAND_TEXT) {
render_text(cmd->text.font, cmd->text.text, cmd->text.pos.x, cmd->text.pos.y, cmd->text.color);
}
if (cmd->type == MU_COMMAND_RECT) {
render_rect(cmd->rect.rect, cmd->rect.color);
}
if (cmd->type == MU_COMMAND_ICON) {
render_icon(cmd->icon.id, cmd->icon.rect, cmd->icon.color);
}
if (cmd->type == MU_COMMAND_CLIP) {
set_clip_rect(cmd->clip.rect);
}
}
See the demo
directory for a usage example.
The layout system is primarily based around rows — Each row
can contain a number of items or columns each column can itself
contain a number of rows and so forth. A row is initialised using the
mu_layout_row()
function, the user should specify the number of items
on the row, an array containing the width of each item, and the height
of the row:
/* initialise a row of 3 items: the first item with a width
** of 90 and the remaining two with the width of 100 */
mu_layout_row(ctx, 3, (int[]) { 90, 100, 100 }, 0);
When a row is filled the next row is started, for example, in the above code 6 buttons immediately after would result in two rows. The function can be called again to begin a new row.
As well as absolute values, width and height can be specified as 0
which will result in the Context's style.size
value being used, or a
negative value which will size the item relative to the right/bottom edge,
thus if we wanted a row with a small button at the left, a textbox filling
most the row and a larger button at the right, we could do the following:
mu_layout_row(ctx, 3, (int[]) { 30, -90, -1 }, 0);
mu_button(ctx, "X");
mu_textbox(ctx, buf, sizeof(buf));
mu_button(ctx, "Submit");
If the items
parameter is -1
, the widths
parameter is ignored
and controls will continue to be added to the row at the width last
specified by mu_layout_width()
or style.size.x
if this function has
not been called:
mu_layout_row(ctx, -1, NULL, 0);
mu_layout_width(ctx, -90);
mu_textbox(ctx, buf, sizeof(buf));
mu_layout_width(ctx, -1);
mu_button(ctx, "Submit");
A column can be started at any point on a row using the
mu_layout_begin_column()
function. Once begun, rows will act inside
the body of the column — all negative size values will be relative to
the column's body as opposed to the body of the container. All new rows
will be contained within this column until the mu_layout_end_column()
function is called.
Internally controls use the mu_layout_next()
function to retrieve the
next screen-positioned-Rect and advance the layout system, you should use
this function when making custom controls or if you want to advance the
layout system without placing a control.
The mu_layout_set_next()
function is provided to set the next layout
Rect explicitly. This will be returned by mu_layout_next()
when it is
next called. By using the relative
boolean you can choose to provide
a screen-space Rect or a Rect which will have the container's position
and scroll offset applied to it. You can peek the next Rect from the
layout system by using the mu_layout_next()
function to retrieve it,
followed by mu_layout_set_next()
to return it:
mu_Rect rect = mu_layout_next(ctx);
mu_layout_set_next(ctx, rect, 0);
If you want to position controls arbitrarily inside a container the
relative
argument of mu_layout_set_next()
should be true:
/* place a (40, 40) sized button at (300, 300) inside the container: */
mu_layout_set_next(ctx, mu_rect(300, 300, 40, 40), 1);
mu_button(ctx, "X");
A Rect set with relative
true will also effect the content_size
of the container, causing it to effect the scrollbars if it exceeds the
width or height of the container's body.
The library provides styling support via the mu_Style
struct and, if you
want greater control over the look, the draw_frame()
callback function.
The mu_Style
struct contains spacing and sizing information, as well
as a colors
array which maps colorid
to mu_Color
. The library uses
the style
pointer field of the context to resolve colors and spacing,
it is safe to change this pointer or modify any fields of the resultant
struct at any point. See microui.h
for the struct's
implementation.
In addition to the style struct the context stores a draw_frame()
callback function which is used whenever the frame of a control needs
to be drawn, by default this function draws a rectangle using the color
of the colorid
argument, with a one-pixel border around it using the
MU_COLOR_BORDER
color.
The library exposes the functions used by built-in controls to allow the
user to make custom controls. A control should take a mu_Context*
value
as its first argument and return a MU_RES_...
value. Your control's
implementation should use mu_layout_next()
to get its destination
Rect and advance the layout system. mu_get_id()
should be used with
some data unique to the control to generate an ID for that control and
mu_update_control()
should be used to update the context's hover
and focus
values based on the mouse input state.
The MU_OPT_HOLDFOCUS
opt value can be passed to mu_update_control()
if we want the control to retain focus when the mouse button is released
— this behaviour is used by textboxes which we want to stay focused
to allow for text input.
A control that acts as a button which displays an integer and, when clicked increments that integer, could be implemented as such:
int incrementer(mu_Context *ctx, int *value) {
mu_Id id = mu_get_id(ctx, &value, sizeof(value));
mu_Rect rect = mu_layout_next(ctx);
mu_update_control(ctx, id, rect, 0);
/* handle input */
int res = 0;
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
(*value)++;
res |= MU_RES_CHANGE;
}
/* draw */
char buf[32];
sprintf(buf, "%d", *value);
mu_draw_control_frame(ctx, id, rect, MU_COLOR_BUTTON, 0);
mu_draw_control_text(ctx, buf, rect, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER);
return res;
}