Fix README update
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / check_changes (push) Has been cancelled
Odin Bindings Update / build (macos-latest) (push) Has been cancelled
Odin Bindings Update / build (ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / commit (push) Has been cancelled

This commit is contained in:
Nic Barker 2025-09-29 13:27:40 +10:00
parent 37675089e3
commit 7874cdb085

275
README.md
View file

@ -59,7 +59,7 @@ Clay_ElementDeclaration sidebarItemConfig = (Clay_ElementDeclaration) {
// Re-useable components are just normal functions
void SidebarItemComponent() {
CLAY(sidebarItemConfig) {
CLAY(id, sidebarItemConfig) {
// children go here...
}
}
@ -85,14 +85,13 @@ int main() {
Clay_BeginLayout();
// An example of laying out a UI with a fixed width sidebar and flexible width main content
CLAY({ .id = CLAY_ID("OuterContainer"), .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = {250,250,255,255} }) {
CLAY({
.id = CLAY_ID("SideBar"),
CLAY(CLAY_ID("OuterContainer"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = {250,250,255,255} }) {
CLAY(CLAY_ID("SideBar"), {
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 },
.backgroundColor = COLOR_LIGHT
}) {
CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) {
CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture } }) {}
CLAY(CLAY_ID("ProfilePictureOuter"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) {
CLAY(CLAY_ID("ProfilePicture"), {.layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture } }) {}
CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} }));
}
@ -101,7 +100,7 @@ int main() {
SidebarItemComponent();
}
CLAY({ .id = CLAY_ID("MainContent"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {}
CLAY(CLAY_ID("MainContent"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {}
}
}
@ -194,16 +193,16 @@ For help starting out or to discuss clay, considering joining [the discord serve
## High Level Documentation
### Building UI Hierarchies
Clay UIs are built using the C macro `CLAY({ 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.
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.
```C
// Parent element with 8px of padding
CLAY({ .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
CLAY(CLAY_ID("parent"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
// Child element 1
CLAY_TEXT(CLAY_STRING("Hello World"), CLAY_TEXT_CONFIG({ .fontSize = 16 }));
// Child element 2 with red background
CLAY({ .backgroundColor = COLOR_RED }) {
CLAY((CLAY_ID("child"), { .backgroundColor = COLOR_RED }) {
// etc
}
}
@ -214,13 +213,13 @@ However, unlike HTML and other declarative DSLs, this macro is just C. As a resu
// Re-usable "components" are just functions that declare more UI
void ButtonComponent(Clay_String buttonText) {
// Red box button with 8px of padding
CLAY({ .layout = { .padding = CLAY_PADDING_ALL(8) }, .backgroundColor = COLOR_RED }) {
CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(8) }, .backgroundColor = COLOR_RED }) {
CLAY_TEXT(buttonText, textConfig);
}
}
// Parent element
CLAY({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
CLAY(CLAY_ID("parent"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
// Render a bunch of text elements
for (int i = 0; i < textArray.length; i++) {
CLAY_TEXT(textArray.elements[i], textConfig);
@ -240,7 +239,7 @@ CLAY({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
### Configuring Layout and Styling UI Elements
The layout and style of clay elements is configured with the [Clay_ElementDeclaration](#clay_elementdeclaration) struct passed to the `CLAY()` macro.
```C
CLAY({ .layout = { .padding = { 8, 8, 8, 8 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
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
}
```
@ -257,18 +256,21 @@ Clay_ElementDeclaration reuseableStyle = (Clay_ElementDeclaration) {
.cornerRadius = { 12, 12, 12, 12 }
};
CLAY(reuseableStyle) {
CLAY(CLAY_ID("box"), reuseableStyle) {
// ...
}
```
### Element IDs
Clay elements can optionally be tagged with a unique identifier using the `.id` field of an element declaration, and with the [CLAY_ID()](#clay_id) convenience macro.
The Clay macro by default accepts an ID as its first argument, which is usually provided by the [CLAY_ID()](#clay_id) convenience macro. Elements can also be created with auto generated IDs, by using the [CLAY_AUTO_ID()](#clay-auto-id) macro.
```C
// Will always produce the same ID from the same input string
CLAY({ .id = CLAY_ID("OuterContainer") }) {}
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](#clay_pointerover) or dimensions.
@ -279,11 +281,11 @@ To avoid having to construct dynamic strings at runtime to differentiate ids in
```C
// This is the equivalent of calling CLAY_ID("Item0"), CLAY_ID("Item1") etc
for (int index = 0; index < items.length; index++) {
CLAY({ .id = CLAY_IDI("Item", index) }) {}
CLAY(CLAY_IDI("Item", index), { ..configuration }) {}
}
```
This ID (or, if not provided, an auto generated 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)
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)
### Mouse, Touch and Pointer Interactions
@ -297,7 +299,7 @@ The function `bool Clay_Hovered()` can be called during element construction or
```C
// An orange button that turns blue when hovered
CLAY({ .backgroundColor = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE }) {
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);
}
@ -318,7 +320,7 @@ void HandleButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerI
ButtonData linkButton = (ButtonData) { .link = "https://github.com/nicbarker/clay" };
// HandleButtonInteraction will be called for each frame the mouse / pointer / touch is inside the button boundaries
CLAY({ .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
CLAY(CLAY_ID("Button"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
Clay_OnHover(HandleButtonInteraction, &linkButton);
CLAY_TEXT(CLAY_STRING("Button"), &headerTextConfig);
}
@ -360,11 +362,11 @@ Clay_UpdateScrollContainers(
);
// ...
// Clay internally tracks the scroll containers offset, and Clay_GetScrollOffset returns the x,y offset of the currently open element
CLAY({ .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) {
CLAY(CLAY_ID("ScrollContainer"), { .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) {
// Scrolling contents
}
// .childOffset can be provided directly if you would prefer to manage scrolling outside of clay
CLAY({ .clip = { .vertical = true, .childOffset = myData.scrollContainer.offset } }) {
CLAY(CLAY_ID("ScrollContainer"), { .clip = { .vertical = true, .childOffset = myData.scrollContainer.offset } }) {
// Scrolling contents
}
```
@ -383,10 +385,10 @@ A classic example use case for floating elements is tooltips and modals.
```C
// The two text elements will be laid out top to bottom, and the floating container
// will be attached to "Outer"
CLAY({ .id = CLAY_ID("Outer"), .layout = { .layoutDirection = TOP_TO_BOTTOM } }) {
CLAY_TEXT(CLAY_ID("Button"), text, &headerTextConfig);
CLAY({ .id = CLAY_ID("Tooltip"), .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } }) {}
CLAY_TEXT(CLAY_ID("Button"), text, &headerTextConfig);
CLAY(CLAY_ID("Outer"), { .layout = { .layoutDirection = TOP_TO_BOTTOM } }) {
CLAY_TEXT(text, &headerTextConfig);
CLAY(CLAY_ID("Tooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } }) {}
CLAY_TEXT(text, &headerTextConfig);
}
```
@ -424,14 +426,11 @@ typedef struct {
// During init
Arena frameArena = (Arena) { .memory = malloc(1024) };
// ...
CLAY(0) {
// Custom elements only take a single pointer, so we need to store the data somewhere
CustomElementData *modelData = (CustomElementData *)(frameArena.memory + frameArena.offset);
*modelData = (CustomElementData) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel };
frameArena.offset += sizeof(CustomElementData);
CLAY({ .custom = { .customData = modelData } }) {}
}
// Custom elements only take a single pointer, so we need to store the data somewhere
CustomElementData *modelData = (CustomElementData *)(frameArena.memory + frameArena.offset);
*modelData = (CustomElementData) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel };
frameArena.offset += sizeof(CustomElementData);
CLAY(CLAY_ID("3DModelViewer"), { .custom = { .customData = modelData } }) {}
// Later during your rendering
switch (renderCommand->commandType) {
@ -682,7 +681,7 @@ See [Scrolling Elements](#scrolling-elements) for more details.
```C
// Create a horizontally scrolling container
CLAY({
CLAY(CLAY_ID("ScrollContainer"), {
.clip = { .horizontal = true, .childOffset = Clay_GetScrollOffset() }
})
```
@ -732,9 +731,9 @@ void HandleButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerD
ButtonData linkButton = (ButtonData) { .link = "https://github.com/nicbarker/clay" };
// HandleButtonInteraction will be called for each frame the mouse / pointer / touch is inside the button boundaries
CLAY({ .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
CLAY(CLAY_ID("Button"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
Clay_OnHover(HandleButtonInteraction, &buttonData);
CLAY_TEXT(CLAY_STRING("Button"), &headerTextConfig);
CLAY_TEXT(CLAY_STRING("Click me!"), &headerTextConfig);
}
```
@ -788,13 +787,40 @@ Returns a [Clay_ElementId](#clay_elementid) for the provided id string, used for
**Examples**
```C
// Define an element with 16px of x and y padding
CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
CLAY(CLAY_ID("Outer"), { .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
// A nested child element
CLAY({ .id = CLAY_ID("SideBar"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
CLAY(CLAY_ID("SideBar"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
// Children laid out top to bottom with a 16 px gap between them
}
// A vertical scrolling container with a colored background
CLAY({
CLAY(CLAY_ID("ScrollContainer"), {
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 },
.backgroundColor = { 200, 200, 100, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(10),
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }
}) {
// child elements
}
}
```
---
### CLAY_AUTO_ID()
A version of the core [CLAY()](#clay) element creation macro that generates an ID automatically instead of requiring it as the first argument.
Note that under the hood this ID is generated in the same way as [CLAY_ID_LOCAL()](#clay_id_local), which is based on the element's position in the hierarchy, and may chance between layout calls if elements are added / removed from the hierarchy before the element is defined. As a result, for transitions & retained mode backends to work correctly, IDs should be specified.
```C
// Note that CLAY_AUTO_ID only takes one argument: the configuration
CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
// A nested child element
CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
// Children laid out top to bottom with a 16 px gap between them
}
// A vertical scrolling container with a colored background
CLAY_AUTO_ID({
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 },
.backgroundColor = { 200, 200, 100, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(10),
@ -931,8 +957,7 @@ To regenerate the same ID outside of layout declaration when using utility funct
```C
// Tag a button with the Id "Button"
CLAY({
.id = CLAY_ID("Button"),
CLAY(CLAY_ID("Button"), {
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }
}) {
// ...children
@ -1065,18 +1090,9 @@ typedef struct {
**Fields**
**`.id`** - `Clay_ElementID`
`CLAY({ .id = CLAY_ID("FileButton") })`
Uses [Clay_ElementId](#clay_elementid). Tags the element with an ID that can be later used to query data about the element, and gives it a human readable name in the debug tools.
IDs are typically generated using the [CLAY_ID](#clay_id), [CLAY_IDI](#clay_idi), [CLAY_ID_LOCAL](#clay_id_local) and [CLAY_IDI_LOCAL](#clay_idi_local) macros.
---
**`.layout`** - `Clay_LayoutConfig`
`CLAY({ .layout = { .padding = { 16, 16, 12, 12 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } })`
`CLAY(CLAY_ID("Element"), { .layout = { .padding = { 16, 16, 12, 12 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } })`
Uses [Clay_LayoutConfig](#clay_layoutconfig). Controls various settings related to _layout_, which can be thought of as "the size and position of this element and its children".
@ -1084,7 +1100,7 @@ Uses [Clay_LayoutConfig](#clay_layoutconfig). Controls various settings related
**`.backgroundColor`** - `Clay_Color`
`CLAY({ .backgroundColor = {120, 120, 120, 255} } })`
`CLAY(CLAY_ID("Element"), { .backgroundColor = {120, 120, 120, 255} } })`
Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout.
@ -1092,7 +1108,7 @@ Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values betwe
**`.cornerRadius`** - `float`
`CLAY({ .cornerRadius = { .topLeft = 16, .topRight = 16, .bottomLeft = 16, .bottomRight = 16 } })`
`CLAY(CLAY_ID("Element"), { .cornerRadius = { .topLeft = 16, .topRight = 16, .bottomLeft = 16, .bottomRight = 16 } })`
Defines the radius in pixels for the arc of rectangle corners (`0` is square, `rectangle.width / 2` is circular).
@ -1102,7 +1118,7 @@ Note that the `CLAY_CORNER_RADIUS(radius)` function-like macro is available to p
**`.aspectRatio`** - `Clay_AspectRatioElementConfig`
`CLAY({ .aspectRatio = 1 })`
`CLAY(CLAY_ID("Element"), { .aspectRatio = 1 })`
Uses [Clay_AspectRatioElementConfig](#clay_aspectratioelementconfig). Configures the element as an aspect ratio scaling element. Especially useful for rendering images, but can also be used to enforce a fixed width / height ratio of other elements.
@ -1110,7 +1126,7 @@ Uses [Clay_AspectRatioElementConfig](#clay_aspectratioelementconfig). Configures
**`.image`** - `Clay_ImageElementConfig`
`CLAY({ .image = { .imageData = &myImage } })`
`CLAY(CLAY_ID("Element"), { .image = { .imageData = &myImage } })`
Uses [Clay_ImageElementConfig](#clay_imageelementconfig). Configures the element as an image element. Causes a render command with type `IMAGE` to be emitted.
@ -1118,7 +1134,7 @@ Uses [Clay_ImageElementConfig](#clay_imageelementconfig). Configures the element
**`.floating`** - `Clay_FloatingElementConfig`
`CLAY({ .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } })`
`CLAY(CLAY_ID("Element"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } })`
Uses [Clay_FloatingElementConfig](#clay_floatingelementconfig). Configures the element as an floating element, which allows it to stack "in front" and "on top" of other elements without affecting sibling or parent size or position.
@ -1126,7 +1142,7 @@ Uses [Clay_FloatingElementConfig](#clay_floatingelementconfig). Configures the e
**`.custom`** - `Clay_CustomElementConfig`
`CLAY({ .custom = { .customData = &my3DModel } })`
`CLAY(CLAY_ID("Element"), { .custom = { .customData = &my3DModel } })`
Uses [Clay_CustomElementConfig](#clay_customelementconfig). Configures the element as a custom element, which allows you to pass custom data through to the renderer. Causes a render command with type `CUSTOM` to be emitted.
@ -1134,7 +1150,7 @@ Uses [Clay_CustomElementConfig](#clay_customelementconfig). Configures the eleme
**`.clip`** - `Clay_ClipElementConfig`
`CLAY({ .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } })`
`CLAY(CLAY_ID("Element"), { .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } })`
Uses [Clay_ClipElementConfig](#clay_scrollelementconfig). Configures the element as a clip element, which causes child elements to be clipped / masked if they overflow, and together with the functions listed in [Scrolling Elements](#scrolling-elements) enables scrolling of child contents.
@ -1144,7 +1160,7 @@ Uses [Clay_ClipElementConfig](#clay_scrollelementconfig). Configures the element
**`.border`** - `Clay_BorderElementConfig`
`CLAY({ .border = { .width = { .left = 5 }, .color = COLOR_BLUE } })`
`CLAY(CLAY_ID("Element"), { .border = { .width = { .left = 5 }, .color = COLOR_BLUE } })`
Uses [Clay_BorderElementConfig](#clay_borderelementconfig). Configures the element as a border element, which instructs the renderer to draw coloured border lines along the perimeter of this element's bounding box. Causes a render command with type `BORDER` to be emitted.
@ -1152,7 +1168,7 @@ Uses [Clay_BorderElementConfig](#clay_borderelementconfig). Configures the eleme
**`.userData`** - `void *`
`CLAY({ .userData = &extraData })`
`CLAY(CLAY_ID("Element"), { .userData = &extraData })`
Transparently passes a pointer through to the corresponding [Clay_RenderCommands](#clay_rendercommand)s generated by this element.
@ -1164,13 +1180,13 @@ Transparently passes a pointer through to the corresponding [Clay_RenderCommands
// Declare a reusable rectangle config, with a purple color and 10px rounded corners
Clay_RectangleElementConfig rectangleConfig = (Clay_RectangleElementConfig) { .color = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10) };
// Declare a rectangle element using a reusable config
CLAY(rectangleConfig)) {}
CLAY(CLAY_ID("Box"), rectangleConfig) {}
// Declare a retangle element using an inline config
CLAY({ .color = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10) })) {
CLAY(CLAY_ID("BoxInline"), { .color = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10) })) {
// child elements
}
// Declare a scrolling container with a colored background
CLAY({
CLAY(CLAY_ID("ScrollingContainer"), {
.backgroundColor = { 200, 200, 100, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(10)
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }
@ -1210,7 +1226,7 @@ Clay_LayoutConfig {
**`.layoutDirection`** - `Clay_LayoutDirection`
`CLAY({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } })`
`CLAY(CLAY_ID("Element"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } })`
Controls the axis / direction in which child elements are laid out. Available options are `CLAY_LEFT_TO_RIGHT` (default) and `CLAY_TOP_TO_BOTTOM`.
@ -1222,7 +1238,7 @@ _Did you know that "left to right" and "top to bottom" both have 13 letters?_
**`.padding`** - `Clay_Padding`
`CLAY({ .layout = { .padding = { .left = 16, .right = 16, .top = 8, .bottom = 8 } } })`
`CLAY(CLAY_ID("Element"), { .layout = { .padding = { .left = 16, .right = 16, .top = 8, .bottom = 8 } } })`
Controls white-space "padding" around the **outside** of child elements.
@ -1232,7 +1248,7 @@ Controls white-space "padding" around the **outside** of child elements.
**`.childGap`** - `uint16_t`
`CLAY({ .layout = { .childGap = 16 } })`
`CLAY(CLAY_ID("Element"), { .layout = { .childGap = 16 } })`
Controls the white-space **between** child elements as they are laid out. When `.layoutDirection` is `CLAY_LEFT_TO_RIGHT` (default), this will be horizontal space, whereas for `CLAY_TOP_TO_BOTTOM` it will be vertical space.
@ -1242,7 +1258,7 @@ Controls the white-space **between** child elements as they are laid out. When `
**`.childAlignment`** - `Clay_ChildAlignment`
`CLAY({ .layout = { .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER } } })`
`CLAY(CLAY_ID("Element"), { .layout = { .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER } } })`
Controls the alignment of children relative to the height and width of the parent container. Available options are:
```C
@ -1256,7 +1272,7 @@ Controls the alignment of children relative to the height and width of the paren
**`.sizing`** - `Clay_Sizing`
`CLAY({ .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_PERCENT(0.5) } } })`
`CLAY(CLAY_ID("Element"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_PERCENT(0.5) } } })`
Controls how final width and height of element are calculated. The same configurations are available for both the `.width` and `.height` axis. There are several options:
@ -1276,7 +1292,7 @@ Controls how final width and height of element are calculated. The same configur
**Example Usage**
```C
CLAY({ .id = CLAY_ID("Button"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16, .childGap = 16) } }) {
CLAY(CLAY_ID("Button"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16, .childGap = 16) } }) {
// Children will be laid out vertically with 16px of padding around and between
}
```
@ -1286,7 +1302,7 @@ CLAY({ .id = CLAY_ID("Button"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTO
### Clay_ImageElementConfig
**Usage**
`CLAY({ .image = { ...image config } }) {}`
`CLAY(CLAY_ID("Element"), { .image = { ...image config } }) {}`
**Clay_ImageElementConfig** configures a clay element to render an image as its background.
@ -1302,7 +1318,7 @@ Clay_ImageElementConfig {
**`.imageData`** - `void *`
`CLAY({ .image = { .imageData = &myImage } }) {}`
`CLAY(CLAY_ID("Image"), { .image = { .imageData = &myImage } }) {}`
`.imageData` is a generic void pointer that can be used to pass through image data to the renderer.
@ -1310,7 +1326,7 @@ Clay_ImageElementConfig {
// Load an image somewhere in your code
YourImage profilePicture = LoadYourImage("profilePicture.png");
// Note that when rendering, .imageData will be void* type.
CLAY({ .image = { .imageData = &profilePicture } }) {}
CLAY(CLAY_ID("Image"), { .image = { .imageData = &profilePicture } }) {}
```
**Examples**
@ -1321,9 +1337,9 @@ YourImage profilePicture = LoadYourImage("profilePicture.png");
// Declare a reusable image config
Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture };
// Declare an image element using a reusable config
CLAY({ .image = imageConfig }) {}
CLAY(CLAY_ID("Image"), { .image = imageConfig }) {}
// Declare an image element using an inline config
CLAY({ .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {}
CLAY(CLAY_ID("ImageInline"), { .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {}
// Rendering example
YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData;
```
@ -1338,7 +1354,7 @@ Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_
**Usage**
`CLAY({ .aspectRatio = 16.0 / 9.0 }) {}`
`CLAY(CLAY_ID("Aspect"), { .aspectRatio = 16.0 / 9.0 }) {}`
**Clay_AspectRatioElementConfig** configures a clay element to enforce a fixed width / height ratio in its final dimensions. Mostly used for image elements, but can also be used for non image elements.
@ -1354,11 +1370,11 @@ Clay_AspectRatioElementConfig {
**`.aspectRatio`** - `float`
`CLAY({ .aspectRatio = { .aspectRatio = 16.0 / 9.0 } }) {}`
`CLAY(CLAY_ID("Aspect"), { .aspectRatio = { .aspectRatio = 16.0 / 9.0 } }) {}`
or alternatively, as C will automatically pass the value to the first nested struct field:
`CLAY({ .aspectRatio = 16.0 / 9.0 }) {}`
`CLAY(CLAY_ID("Aspect"), { .aspectRatio = 16.0 / 9.0 }) {}`
**Examples**
@ -1366,7 +1382,7 @@ or alternatively, as C will automatically pass the value to the first nested str
// Load an image somewhere in your code
YourImage profilePicture = LoadYourImage("profilePicture.png");
// Declare an image element that will grow along the X axis while maintaining its original aspect ratio
CLAY({
CLAY(CLAY_ID("ProfilePicture"), {
.layout = { .width = CLAY_SIZING_GROW() },
.aspectRatio = profilePicture.width / profilePicture.height,
.image = { .imageData = &profilePicture },
@ -1378,7 +1394,7 @@ CLAY({
### Clay_ImageElementConfig
**Usage**
`CLAY({ .image = { ...image config } }) {}`
`CLAY(CLAY_ID("Image"), { .image = { ...image config } }) {}`
**Clay_ImageElementConfig** configures a clay element to render an image as its background.
@ -1394,7 +1410,7 @@ Clay_ImageElementConfig {
**`.imageData`** - `void *`
`CLAY({ .image = { .imageData = &myImage } }) {}`
`CLAY(CLAY_ID("Image"), { .image = { .imageData = &myImage } }) {}`
`.imageData` is a generic void pointer that can be used to pass through image data to the renderer.
@ -1402,7 +1418,7 @@ Clay_ImageElementConfig {
// Load an image somewhere in your code
YourImage profilePicture = LoadYourImage("profilePicture.png");
// Note that when rendering, .imageData will be void* type.
CLAY({ .image = { .imageData = &profilePicture } }) {}
CLAY(CLAY_ID("Image"), { .image = { .imageData = &profilePicture } }) {}
```
Note: for an image to maintain its original aspect ratio when using dynamic scaling, the [.aspectRatio](#clay_aspectratioelementconfig) config option must be used.
@ -1415,9 +1431,9 @@ YourImage profilePicture = LoadYourImage("profilePicture.png");
// Declare a reusable image config
Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture };
// Declare an image element using a reusable config
CLAY({ .image = imageConfig }) {}
CLAY(CLAY_ID("Image"), { .image = imageConfig }) {}
// Declare an image element using an inline config
CLAY({ .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {}
CLAY(CLAY_ID("ImageInline"), { .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {}
// Rendering example
YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData;
```
@ -1432,7 +1448,7 @@ Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_
**Usage**
`CLAY({ .clip = { ...clip config } }) {}`
`CLAY(CLAY_ID("ScrollBox"), { .clip = { ...clip config } }) {}`
**Notes**
@ -1453,7 +1469,7 @@ Clay_ClipElementConfig {
**`.horizontal`** - `bool`
`CLAY({ .clip = { .horizontal = true } })`
`CLAY(CLAY_ID("HorizontalScroll"), { .clip = { .horizontal = true } })`
Enables or disables horizontal clipping for this container element.
@ -1461,7 +1477,7 @@ Enables or disables horizontal clipping for this container element.
**`.vertical`** - `bool`
`CLAY({ .clip = { .vertical = true } })`
`CLAY(LAY_ID("VerticalScroll"), { .clip = { .vertical = true } })`
Enables or disables vertical clipping for this container element.
@ -1476,9 +1492,9 @@ Enabling clip for an element will result in two additional render commands:
**Examples**
```C
CLAY({ .clip = { .vertical = true } }) {
CLAY(CLAY_ID("ScrollOuter"), { .clip = { .vertical = true } }) {
// Create child content with a fixed height of 5000
CLAY({ .id = CLAY_ID("ScrollInner"), .layout = { .sizing = { .height = CLAY_SIZING_FIXED(5000) } } }) {}
CLAY(CLAY_ID("ScrollInner"), { .layout = { .sizing = { .height = CLAY_SIZING_FIXED(5000) } } }) {}
}
```
@ -1488,7 +1504,7 @@ CLAY({ .clip = { .vertical = true } }) {
**Usage**
`CLAY({ .border = { ...border config } }) {}`
`CLAY(CLAY_ID("Border"), { .border = { ...border config } }) {}`
**Notes**
@ -1516,7 +1532,7 @@ typedef struct Clay_BorderElementConfig
**`.color`** - `Clay_Color`
`CLAY({ .border = { .color = { 255, 0, 0, 255 } } })`
`CLAY(CLAY_ID("Border"), { .border = { .color = { 255, 0, 0, 255 } } })`
Uses [Clay_Color](#clay_color). Specifies the shared color for all borders configured by this element. Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout.
@ -1524,7 +1540,7 @@ Uses [Clay_Color](#clay_color). Specifies the shared color for all borders confi
**`.width`** - `Clay_BorderWidth`
`CLAY({ .border = { .width = { .left = 2, .right = 10 } } })`
`CLAY(CLAY_ID("Border"), { .border = { .width = { .left = 2, .right = 10 } } })`
Indicates to the renderer that a border of `.color` should be draw at the specified edges of the bounding box, **inset and overlapping the box contents by `.width`**.
@ -1534,7 +1550,7 @@ Note:
**`.width.betweenChildren`**
`CLAY({ .border = { .width = { .betweenChildren = 2 } }, .color = COLOR_RED })`
`CLAY(CLAY_ID("Border"), { .border = { .width = { .betweenChildren = 2 } }, .color = COLOR_RED })`
Configures the width and color of borders to be drawn between children. These borders will be vertical lines if the parent uses `.layoutDirection = CLAY_LEFT_TO_RIGHT` and horizontal lines if the parent uses `CLAY_TOP_TO_BOTTOM`. Unlike `.left, .top` etc, this option **will generate additional rectangle render commands representing the borders between children.** As a result, the renderer does not need to specifically implement rendering for these border elements.
@ -1544,8 +1560,7 @@ Configures the width and color of borders to be drawn between children. These bo
```C
// 300x300 container with a 1px red border around all the edges
CLAY({
.id = CLAY_ID("OuterBorder"),
CLAY(CLAY_ID("OuterBorder"), {
.layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) } },
.border = { .width = { 1, 1, 1, 1, 0 }, .color = COLOR_RED }
}) {
@ -1553,16 +1568,14 @@ CLAY({
}
// Container with a 3px yellow bottom border
CLAY({
.id = CLAY_ID("OuterBorder"),
CLAY(CLAY_ID("OuterBorder"), {
.border = { .width = { .bottom = 3 }, .color = COLOR_YELLOW }
}) {
// ...
}
// Container with a 5px curved border around the edges, and a 5px blue border between all children laid out top to bottom
CLAY({
.id = CLAY_ID("OuterBorder"),
CLAY(CLAY_ID("OuterBorder"), {
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM },
.border = { .width = { 5, 5, 5, 5, 5 }, .color = COLOR_BLUE }
}) {
@ -1585,7 +1598,7 @@ Rendering of borders and rounded corners is left up to the user. See the provide
**Usage**
`CLAY({ .floating = { ...floating config } }) {}`
`CLAY(CLAY_ID("Floating"), { .floating = { ...floating config } }) {}`
**Notes**
@ -1636,7 +1649,7 @@ Clay_FloatingElementConfig {
**`.offset`** - `Clay_Vector2`
`CLAY({ .floating = { .offset = { -24, -24 } } })`
`CLAY(CLAY_ID("Floating"), { .floating = { .offset = { -24, -24 } } })`
Used to apply a position offset to the floating container _after_ all other layout has been calculated.
@ -1644,7 +1657,7 @@ Used to apply a position offset to the floating container _after_ all other layo
**`.expand`** - `Clay_Dimensions`
`CLAY({ .floating = { .expand = { 16, 16 } } })`
`CLAY(CLAY_ID("Floating"), { .floating = { .expand = { 16, 16 } } })`
Used to expand the width and height of the floating container _before_ laying out child elements.
@ -1652,7 +1665,7 @@ Used to expand the width and height of the floating container _before_ laying ou
**`.zIndex`** - `float`
`CLAY({ .floating = { .zIndex = 1 } })`
`CLAY(CLAY_ID("Floating"), { .floating = { .zIndex = 1 } })`
All floating elements (as well as their entire child hierarchies) will be sorted by `.zIndex` order before being converted to render commands. If render commands are drawn in order, elements with higher `.zIndex` values will be drawn on top.
@ -1660,41 +1673,41 @@ All floating elements (as well as their entire child hierarchies) will be sorted
**`.parentId`** - `uint32_t`
`CLAY({ .floating = { .parentId = Clay_GetElementId("HeaderButton").id } })`
`CLAY(CLAY_ID("Floating"), { .floating = { .parentId = Clay_GetElementId("HeaderButton").id } })`
By default, floating containers will "attach" to the parent element that they are declared inside. However, there are cases where this limitation could cause significant performance or ergonomics problems. `.parentId` allows you to specify a `CLAY_ID().id` to attach the floating container to. The parent element with the matching id can be declared anywhere in the hierarchy, it doesn't need to be declared before or after the floating container in particular.
Consider the following case:
```C
// Load an image somewhere in your code
CLAY({ .id = CLAY_IDI("SidebarButton", 1) }) {
CLAY(CLAY_IDI("SidebarButton", 1), { }) {
// .. some button contents
if (tooltip.attachedButtonIndex == 1) {
CLAY({ /* floating config... */ })
CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ })
}
}
CLAY({ .id = CLAY_IDI("SidebarButton", 2) }) {
CLAY(CLAY_IDI("SidebarButton", 2), { }) {
// .. some button contents
if (tooltip.attachedButtonIndex == 2) {
CLAY({ /* floating config... */ })
CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ })
}
}
CLAY({ .id = CLAY_IDI("SidebarButton", 3) }) {
CLAY(CLAY_IDI("SidebarButton", 3), { }) {
// .. some button contents
if (tooltip.attachedButtonIndex == 3) {
CLAY({ /* floating config... */ })
CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ })
}
}
CLAY({ .id = CLAY_IDI("SidebarButton", 4) }) {
CLAY(CLAY_IDI("SidebarButton", 4), { }) {
// .. some button contents
if (tooltip.attachedButtonIndex == 4) {
CLAY({ /* floating config... */ })
CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ })
}
}
CLAY({ .id = CLAY_IDI("SidebarButton", 5) }) {
CLAY(CLAY_IDI("SidebarButton", 5), { }) {
// .. some button contents
if (tooltip.attachedButtonIndex == 5) {
CLAY({ /* floating config... */ })
CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ })
}
}
```
@ -1703,24 +1716,24 @@ The definition of the above UI is significantly polluted by the need to conditio
```C
// Load an image somewhere in your code
CLAY({ .id = CLAY_IDI("SidebarButton", 1) }) {
CLAY(CLAY_IDI("SidebarButton", 1), { }) {
// .. some button contents
}
CLAY({ .id = CLAY_IDI("SidebarButton", 2) }) {
CLAY(CLAY_IDI("SidebarButton", 2), { }) {
// .. some button contents
}
CLAY({ .id = CLAY_IDI("SidebarButton", 3) }) {
CLAY(CLAY_IDI("SidebarButton", 3), { }) {
// .. some button contents
}
CLAY({ .id = CLAY_IDI("SidebarButton", 4) }) {
CLAY(CLAY_IDI("SidebarButton", 4), { }) {
// .. some button contents
}
CLAY({ .id = CLAY_IDI("SidebarButton", 5) }) {
CLAY(CLAY_IDI("SidebarButton", 5), { }) {
// .. some button contents
}
// Any other point in the hierarchy
CLAY({ .id = CLAY_ID("OptionTooltip"), .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_ID, .parentId = CLAY_IDI("SidebarButton", tooltip.attachedButtonIndex).id }) {
CLAY(CLAY_ID("OptionTooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_ID, .parentId = CLAY_IDI("SidebarButton", tooltip.attachedButtonIndex).id }) {
// Tooltip contents...
}
```
@ -1729,7 +1742,7 @@ CLAY({ .id = CLAY_ID("OptionTooltip"), .floating = { .attachTo = CLAY_ATTACH_TO_
**`.attachment`** - `Clay_FloatingAttachPoints`
`CLAY({ .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } }) {}`
`CLAY(CLAY_ID("Floating"), { .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } }) {}`
In terms of positioning the floating container, `.attachment` specifies
@ -1744,7 +1757,7 @@ For example:
"Attach the LEFT_CENTER of the floating container to the RIGHT_TOP of the parent"
`CLAY({ .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } });`
`CLAY(CLAY_ID("Floating"), { .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } });`
![Screenshot 2024-08-23 at 11 53 24 AM](https://github.com/user-attachments/assets/ebe75e0d-1904-46b0-982d-418f929d1516)
@ -1758,31 +1771,31 @@ Controls whether pointer events like hover and click should pass through to cont
```C
// Horizontal container with three option buttons
CLAY({ .id = CLAY_ID("OptionsList"), .layout = { childGap = 16 } }) {
CLAY({ .id = CLAY_IDI("Option", 1), .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) {
CLAY(CLAY_ID("OptionsList"), { .layout = { childGap = 16 } }) {
CLAY(CLAY_IDI("Option", 1), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) {
CLAY_TEXT(CLAY_STRING("Option 1"), CLAY_TEXT_CONFIG());
}
CLAY({ .id = CLAY_IDI("Option", 2), .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) {
CLAY(CLAY_IDI("Option", 2), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) {
CLAY_TEXT(CLAY_STRING("Option 2"), CLAY_TEXT_CONFIG());
// Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements
CLAY({ .id = CLAY_ID("OptionTooltip"), .floating = { .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_CENTER_TOP } } }) {
CLAY(CLAY_ID("OptionTooltip"), { .floating = { .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_CENTER_TOP } } }) {
CLAY_TEXT(CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG());
}
}
CLAY({ .id = CLAY_IDI("Option", 3), .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) {
CLAY(CLAY_IDI("Option", 3), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) {
CLAY_TEXT(CLAY_STRING("Option 3"), CLAY_TEXT_CONFIG());
}
}
// Floating containers can also be declared elsewhere in a layout, to avoid branching or polluting other UI
for (int i = 0; i < 1000; i++) {
CLAY({ .id = CLAY_IDI("Option", i + 1) }) {
CLAY(CLAY_IDI("Option", i + 1), { }) {
// ...
}
}
// Note the use of "parentId".
// Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements
CLAY({ .id = CLAY_ID("OptionTooltip"), .floating = { .parentId = CLAY_IDI("Option", 2).id, .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_TOP_CENTER } } }) {
CLAY(CLAY_ID("OptionTooltip"), { .floating = { .parentId = CLAY_IDI("Option", 2).id, .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_TOP_CENTER } } }) {
CLAY_TEXT(CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG());
}
```
@ -1799,7 +1812,7 @@ When using `.parentId`, the floating container can be declared anywhere after `B
**Usage**
`CLAY({ .custom = { .customData = &something } }) {}`
`CLAY(CLAY_ID("Custom"), { .custom = { .customData = &something } }) {}`
**Notes**
@ -1858,7 +1871,7 @@ CLAY(0) {
CustomElementData *modelData = (CustomElementData *)(frameArena.memory + frameArena.offset);
*modelData = (CustomElementData) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel };
frameArena.offset += sizeof(CustomElementData);
CLAY({ .custom = { .customData = modelData } }) {}
CLAY(CLAY_ID("3DModelViewer"), { .custom = { .customData = modelData } }) {}
}
// Later during your rendering