High performance UI layout library in C.
Clay (short for C Layout) is a high performance 2D UI layout library.
Take a look at the clay website for an example of clay compiled to wasm and running in the browser, or others in the examples directory.
You can also watch the introduction video for an overview of the motivation behind Clay's development and a short demo of its usage.
An example GUI application built with clay
Download or clone clay.h and include it after defining CLAY_IMPLEMENTATION in one file.
…
The above example, rendered correctly will look something like the following:
In summary, the general order of steps is:
For help starting out or to discuss clay, considering joining the discord server.
Clay UIs are built using the C macro CLAY(id, { configuration }). This macro creates a new empty element in the UI hierarchy, and supports modular customisation of layout, styling and functionality. The CLAY() macro can also be nested, similar to other declarative UI systems like HTML.
Child elements are added by opening a block: {} after calling the CLAY() macro (exactly like you would with an if statement or for loop), and declaring child components inside the braces.
// Parent element with 8px of padding
CLAY(CLAY_ID("parent"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
// Child element 1
CLAY_TEXT(CLAY_STRING("Hello World"), { .fontSize = 16 });
// Child element 2 with red background
CLAY((CLAY_ID("child"), { .backgroundColor = COLOR_RED }) {
// etc
}
}
However, unlike HTML and other declarative DSLs, this macro is just C. As a result, you can use arbitrary C code such as loops, functions and conditions inside your layout declaration code:
…
The layout and style of clay elements is configured with the Clay_ElementDeclaration struct passed to the CLAY() macro.
CLAY(CLAY_ID("box"), { .layout = { .padding = { 8, 8, 8, 8 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
// Children are 8px inset into parent, and laid out top to bottom
}
This macro isn't magic - all it's doing is wrapping the standard designated initializer syntax. e.g. (Clay_ElementDeclaration) { .layout = { .padding = { .left = 8, .right = 8 } ....
See the Clay_ElementDeclaration API for the full list of options.
A Clay_ElementDeclaration struct can be defined in file scope or elsewhere, and reused.
// Define a style in the global / file scope
Clay_ElementDeclaration reuseableStyle = (Clay_ElementDeclaration) {
.layout = { .padding = { .left = 12 } },
.backgroundColor = { 120, 120, 120, 255 },
.cornerRadius = { 12, 12, 12, 12 }
};
CLAY(CLAY_ID("box"), reuseableStyle) {
// ...
}
The Clay macro by default accepts an ID as its first argument, which is usually provided by the CLAY_ID() convenience macro. Elements can also be created with auto generated IDs, by using the CLAY_AUTO_ID() macro.
// Will always produce the same ID from the same input string
CLAY(CLAY_ID("OuterContainer"), { ...configuration }) {}
// Generates a unique ID that may not be the same between two layout calls
CLAY_AUTO_ID({ ...configuration }) {}
Element IDs have two main use cases. Firstly, tagging an element with an ID allows you to query information about the element later, such as its mouseover state or dimensions.
Secondly, IDs are visually useful when attempting to read and modify UI code, as well as when using the built-in debug tools.
To avoid having to construct dynamic strings at runtime to differentiate ids in loops, clay provides the CLAY_IDI(string, index) macro to generate different ids from a single input string. Think of IDI as "ID + Index"
// This is the equivalent of calling CLAY_ID("Item0"), CLAY_ID("Item1") etc
for (int index = 0; index < items.length; index++) {
CLAY(CLAY_IDI("Item", index), { ..configuration }) {}
}
This ID will be forwarded to the final Clay_RenderCommandArray for use in retained mode UIs. Using duplicate IDs may cause some functionality to misbehave (i.e. if you're trying to attach a floating container to a specific element with ID that is duplicated, it may not attach to the one you expect)
Clay provides several functions for handling mouse and pointer interactions.
All pointer interactions depend on the function void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) being called after each mouse position update and before any other clay functions.
During UI declaration
The function bool Clay_Hovered() can be called during element construction or in the body of an element, and returns true if the mouse / pointer is over the currently open element.
// An orange button that turns blue when hovered
CLAY(CLAY_ID("Button"), { .backgroundColor = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE }) {
bool buttonHovered = Clay_Hovered();
CLAY_TEXT(buttonHovered ? CLAY_STRING("Hovered") : CLAY_STRING("Hover me!"), headerTextConfig);
}
The function void Clay_OnHover() allows you to attach a function pointer to the currently open element, which will be called if the mouse / pointer is over the element.
…
Before / After UI declaration
If you want to query mouse / pointer overlaps outside layout declarations, you can use the function bool Clay_PointerOver(Clay_ElementId id), which takes an element id and returns a bool representing whether the current pointer position is within its bounding box.
// Reminder: Clay_SetPointerState must be called before functions that rely on pointer position otherwise it will have no effect
Clay_Vector2 mousePosition = { x, y };
Clay_SetPointerState(mousePosition, mouseButtonDown(0));
// ...
// If profile picture was clicked
if (mouseButtonDown(0) && Clay_PointerOver(Clay_GetElementId("ProfilePicture"))) {
// Handle profile picture clicked
}
Note that the bounding box queried by Clay_PointerOver is from the last frame. This generally shouldn't make a difference except in the case of animations that move at high speed.
If this is an issue for you, performing layout twice per frame with the same data will give you the correct interaction the second time.
Elements are configured as scrollable with the .clip configuration. Clipping instructs the renderer to not draw any pixels outside the clipped ele