QUI is a quick and simple user interface library for C.
The library is meant to be extremely simple to drop into a project and get a quick UI up and running for prototyping, visualizing, and/or debugging. It isn’t a desktop environment or a full featured widget library. Use it when you need basic windows, drawing primitives, and maybe a button or two.
The only documentation is this README. The README contains everything you’ll ever need when using QUI: how to use it, how to build it, how to hack it, a tutorial, and a complete reference.
Only macOS is supported at this time. You will need either Xcode or the CLI developer tools.
Just type make
in the qui
project directory.
$ make
This will produce a static library libqui.a
in the project
directory.
Copy libqui.a
and the headers in the include
directory to a
project. Tell the build system where to find the headers and library
and direct the linker to link libqui
.
The simplest QUI program is
#include "qui.h"
int main(int argc, char **argv)
{
return qu_main(argc, argv, NULL);
}
On macOS this creates a program with a default dock icon, a default menu bar, and a default window.
While the program is completely functional, it isn’t very interesting. You can move the window and quit—that’s about it. While a program that does nothing is nonsensical, it highlights the philosopy of QUI: quick and easy user interfaces with no bullshit!
QUI makes use of function pointers. Function pointers might sound
scary, but in QUI it merely means you’ll pass the names of some
function you create to some QUI functions. In this “hello world”
example the two user created functions are btn_action_cb
and
app_init_cb
, the names can be anything, but their type signature
must match the specified type signature.
#include "qui.h"
#include <stdio.h>
void btn_action_cb(QuButton *btn, QuEvent e)
{
fprintf(stderr, "[action] %s\n", button_title(btn));
}
void app_init_cb(QuApp *app)
{
QuWindow *win = QuWindowA();
QuButton *btn = QuButtonA();
window_set_frame(win, QuRectS(0, 0, 192, 96));
window_center(win);
view_set_frame(btn, QuRectS(32, 32, 128, 32));
button_set_action_func(btn, btn_action_cb);
button_set_title(btn, "hello, world!");
window_add_subview(win, btn);
window_show(win);
}
int main(int argc, char **argv)
{
return qu_main(argc, argv, app_init_cb);
}
This creates a program with a small window and a single button titled “hello, world!”
Quick. Easy. Couldn’t be much simpler. That’s the QUI way.
Types are prefixed with Qu
. There are opaque object references
(QuApp, QuWindow, QuButton, etc.) and small structs (QuRect, QuPoint,
QuRGBA, etc.).
Opaque references are malloc’d with a constructor function that ends
with A
and you’re responsible for freeing the returned reference.
Small structs are created on the stack and no manual memory management
is needed, they end with a S
.
QuApp *app = QuAppA(); // A = allocate in the heap
QuRect rect = QuRectS(); // S = stack allocation
Types are names that follow the convention QuTypeName
where Qu
is the prefix and TypeName is App, Window, View, etc. in PascalCase.
Functions begin with the lowercase type name without the prefix and take an object of the named type as the first argument.
void button_set_title(QuButton *btn, const char *title);
void path_line_to(QuPath *path, QuPoint pos);
The exceptions are graphics context functions like fill_path
,
stroke_rect
, and others when the names are not likely to collide;
functions that do not operate on QUI types like qu_main
; and utility
functions that would otherwise be clunky if the names were too long
like qu_w
and qu_midx
.