Compare commits

...

14 commits

Author SHA1 Message Date
Harrison Lambeth 9853cba679
Merge 38bb241ced into fd97d8179e 2025-10-30 07:10:03 +01:00
Daniel Mayovskiy fd97d8179e
[Renderers/termbox] fixed horizontal text culling bug (#525)
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
2025-10-23 12:58:39 +11:00
Daniel Mayovskiy 7216815536
Fixed termbox2 demo build, added scroll functionality (#523) 2025-10-23 12:57:11 +11:00
Thomas Anderson 83129995f7
[Examples/official-website] updated paths in build.sh 2025-10-23 12:56:20 +11:00
Daniel Mayovskiy 588b93196c
[Renderers/termbox] Fixing termbox2-image-demo build error (#524)
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
2025-10-02 11:21:11 +10:00
github-actions[bot] 382dcde89d [bindings/odin] Update Odin bindings 2025-10-02 01:17:05 +00:00
elmfrain c6442bd192
[Bug Fix] Multiple Floating Elements Cannot Use Clay_Hovered() (#461)
Co-authored-by: Nic Barker <contact+github@nicbarker.com>
2025-10-02 11:14:17 +10:00
Nic Barker 7874cdb085 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
2025-09-29 13:27:40 +10:00
Harrison Lambeth 38bb241ced small fixes 2025-02-10 23:14:44 -07:00
Harrison Lambeth f4933c6669 small refactor 2025-02-10 23:14:44 -07:00
Harrison Lambeth 61ba36753b Fix Clay_OnHover 2025-02-10 23:14:44 -07:00
Harrison Lambeth 4f4605eff9 Add proper support for function arguments 2025-02-10 23:14:44 -07:00
Harrison Lambeth 7c65f31f46 Some fixes after rebasing 2025-02-10 23:14:44 -07:00
Harrison Lambeth 01025e9157 initial pass 2025-02-10 23:14:44 -07:00
28 changed files with 1471 additions and 144 deletions

7
.gitignore vendored
View file

@ -4,4 +4,9 @@ cmake-build-release/
.idea/
node_modules/
*.dSYM
.vs/
.vs/
bindings/odin/clay-odin/tmp/
generator/__pycache__/
generator/generators/__pycache__/

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

12
clay.h
View file

@ -1146,6 +1146,7 @@ typedef struct {
Clay_LayoutConfig *layoutConfig;
Clay__ElementConfigArraySlice elementConfigs;
uint32_t id;
uint16_t floatingChildrenCount;
} Clay_LayoutElement;
CLAY__ARRAY_DEFINE(Clay_LayoutElement, Clay_LayoutElementArray)
@ -1776,7 +1777,8 @@ Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) {
Clay_ElementId Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) {
Clay_Context* context = Clay_GetCurrentContext();
Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2));
Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length, parentElement->id);
uint32_t offset = parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount;
Clay_ElementId elementId = Clay__HashNumber(offset, parentElement->id);
openLayoutElement->id = elementId.id;
Clay__AddHashMapItem(elementId, openLayoutElement);
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
@ -1917,9 +1919,15 @@ void Clay__CloseElement(void) {
// Close the currently open element
int32_t closingElementIndex = Clay__int32_tArray_RemoveSwapback(&context->openLayoutElementStack, (int)context->openLayoutElementStack.length - 1);
// Get the currently open parent
openLayoutElement = Clay__GetOpenLayoutElement();
if (!elementIsFloating && context->openLayoutElementStack.length > 1) {
if (context->openLayoutElementStack.length > 1) {
if(elementIsFloating) {
openLayoutElement->floatingChildrenCount++;
return;
}
openLayoutElement->childrenOrTextContent.children.length++;
Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, closingElementIndex);
}

View file

@ -15,5 +15,5 @@ mkdir -p build/clay \
-Wl,--initial-memory=6553600 \
-o build/clay/index.wasm \
main.c \
&& cp index.html build/clay/index.html && cp -r fonts/ build/clay/fonts \
&& cp index.html build/clay/index.html && cp -r images/ build/clay/images
&& cp index.html build/index.html && cp -r fonts/ build/clay/fonts \
&& cp -r images/ build/clay/images

View file

@ -8,7 +8,7 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8"
GIT_TAG "ffd159c2a6106dd5eef338a6702ad15d4d4aa809"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
@ -17,7 +17,7 @@ FetchContent_MakeAvailable(termbox2)
FetchContent_Declare(
stb
GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c"
GIT_TAG "fede005abaf93d9d7f3a679d1999b2db341b360f"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)

View file

@ -90,7 +90,7 @@ void component_text_pair(const char *key, const char *value)
void component_termbox_settings(void)
{
CLAY_AUTO_ID({
CLAY(CLAY_ID("Termbox Settings"), {
.floating = {
.attachTo = CLAY_ATTACH_TO_PARENT,
.zIndex = 1,
@ -509,13 +509,18 @@ Clay_RenderCommandArray CreateLayout(clay_tb_image *image1, clay_tb_image *image
{
Clay_BeginLayout();
CLAY_AUTO_ID({
.clip = {
.vertical = false,
.horizontal = true,
.childOffset = Clay_GetScrollOffset(),
},
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.x = CLAY_ALIGN_X_LEFT,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 64
@ -714,12 +719,12 @@ void handle_termbox_events(void)
break;
}
case TB_KEY_MOUSE_WHEEL_UP: {
Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() };
Clay_Vector2 scrollDelta = { 0.5 * Clay_Termbox_Cell_Width(), 0 };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
case TB_KEY_MOUSE_WHEEL_DOWN: {
Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() };
Clay_Vector2 scrollDelta = { -0.5 * Clay_Termbox_Cell_Width(), 0 };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}

View file

@ -10,7 +10,7 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8"
GIT_TAG "ffd159c2a6106dd5eef338a6702ad15d4d4aa809"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
@ -19,7 +19,7 @@ FetchContent_MakeAvailable(termbox2)
FetchContent_Declare(
stb
GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c"
GIT_TAG "fede005abaf93d9d7f3a679d1999b2db341b360f"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)

0
generator/__init__.py Normal file
View file

83
generator/cli.py Normal file
View file

@ -0,0 +1,83 @@
import argparse
import logging
import json
from pathlib import Path
from generators.base_generator import BaseGenerator
from generators.odin_generator import OdinGenerator
from parser import parse_headers
logger = logging.getLogger(__name__)
GeneratorMap = dict[str, type[BaseGenerator]]
GENERATORS = {
'odin': OdinGenerator,
}
def main() -> None:
arg_parser = argparse.ArgumentParser(description='Generate clay bindings')
# Directories
arg_parser.add_argument('input_files', nargs='+', type=str, help='Input header files')
arg_parser.add_argument('--output-dir', type=str, help='Output directory', required=True)
arg_parser.add_argument('--tmp-dir', type=str, help='Temporary directory')
# Generators
arg_parser.add_argument('--generator', type=str, choices=list(GENERATORS.keys()), help='Generators to run', required=True)
# Logging
arg_parser.add_argument('--verbose', action='store_true', help='Verbose logging')
args = arg_parser.parse_args()
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
log_handler = logging.StreamHandler()
log_handler.setFormatter(log_formatter)
if args.verbose:
logging.basicConfig(level=logging.DEBUG, handlers=[log_handler])
else:
logging.basicConfig(level=logging.INFO, handlers=[log_handler])
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
if args.tmp_dir:
tmp_dir = Path(args.tmp_dir)
else:
tmp_dir = output_dir / 'tmp'
tmp_dir.mkdir(parents=True, exist_ok=True)
fake_libc_include_path = Path(__file__).parent / 'fake_libc_include'
input_files = list(fake_libc_include_path.glob('*.h')) + [Path(f) for f in args.input_files]
logger.info(f'Input files: {input_files}')
logger.info(f'Output directory: {output_dir}')
logger.info(f'Temporary directory: {tmp_dir}')
logger.info(f'Generator: {args.generator}')
logger.info('Parsing headers')
extracted_symbols = parse_headers(input_files, tmp_dir)
with open(tmp_dir / 'extracted_symbols.json', 'w') as f:
f.write(json.dumps({
'structs': extracted_symbols.structs,
'enums': extracted_symbols.enums,
'functions': extracted_symbols.functions,
}, indent=2))
logger.info('Generating bindings')
generator = GENERATORS[args.generator](extracted_symbols)
generator.generate()
logger.debug(f'Generated bindings:')
# for file_name, content in generator.get_outputs().items():
# logger.debug(f'{file_name}:')
# logger.debug(content)
# logger.debug('\n')
tmp_outputs_dir = tmp_dir / 'generated'
tmp_outputs_dir.mkdir(parents=True, exist_ok=True)
generator.write_outputs(tmp_outputs_dir)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,262 @@
#ifndef _FAKE_DEFINES_H
#define _FAKE_DEFINES_H
#define NULL 0
#define BUFSIZ 1024
#define FOPEN_MAX 20
#define FILENAME_MAX 1024
#ifndef SEEK_SET
#define SEEK_SET 0 /* set file offset to offset */
#endif
#ifndef SEEK_CUR
#define SEEK_CUR 1 /* set file offset to current plus offset */
#endif
#ifndef SEEK_END
#define SEEK_END 2 /* set file offset to EOF plus offset */
#endif
#define __LITTLE_ENDIAN 1234
#define LITTLE_ENDIAN __LITTLE_ENDIAN
#define __BIG_ENDIAN 4321
#define BIG_ENDIAN __BIG_ENDIAN
#define __BYTE_ORDER __LITTLE_ENDIAN
#define BYTE_ORDER __BYTE_ORDER
#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0
#define SCHAR_MIN -128
#define SCHAR_MAX 127
#define CHAR_MIN -128
#define CHAR_MAX 127
#define UCHAR_MAX 255
#define SHRT_MIN -32768
#define SHRT_MAX 32767
#define USHRT_MAX 65535
#define INT_MIN -2147483648
#define INT_MAX 2147483647
#define UINT_MAX 4294967295U
#define LONG_MIN -9223372036854775808L
#define LONG_MAX 9223372036854775807L
#define ULONG_MAX 18446744073709551615UL
#define RAND_MAX 32767
/* C99 inttypes.h defines */
#define PRId8 "d"
#define PRIi8 "i"
#define PRIo8 "o"
#define PRIu8 "u"
#define PRIx8 "x"
#define PRIX8 "X"
#define PRId16 "d"
#define PRIi16 "i"
#define PRIo16 "o"
#define PRIu16 "u"
#define PRIx16 "x"
#define PRIX16 "X"
#define PRId32 "d"
#define PRIi32 "i"
#define PRIo32 "o"
#define PRIu32 "u"
#define PRIx32 "x"
#define PRIX32 "X"
#define PRId64 "d"
#define PRIi64 "i"
#define PRIo64 "o"
#define PRIu64 "u"
#define PRIx64 "x"
#define PRIX64 "X"
#define PRIdLEAST8 "d"
#define PRIiLEAST8 "i"
#define PRIoLEAST8 "o"
#define PRIuLEAST8 "u"
#define PRIxLEAST8 "x"
#define PRIXLEAST8 "X"
#define PRIdLEAST16 "d"
#define PRIiLEAST16 "i"
#define PRIoLEAST16 "o"
#define PRIuLEAST16 "u"
#define PRIxLEAST16 "x"
#define PRIXLEAST16 "X"
#define PRIdLEAST32 "d"
#define PRIiLEAST32 "i"
#define PRIoLEAST32 "o"
#define PRIuLEAST32 "u"
#define PRIxLEAST32 "x"
#define PRIXLEAST32 "X"
#define PRIdLEAST64 "d"
#define PRIiLEAST64 "i"
#define PRIoLEAST64 "o"
#define PRIuLEAST64 "u"
#define PRIxLEAST64 "x"
#define PRIXLEAST64 "X"
#define PRIdFAST8 "d"
#define PRIiFAST8 "i"
#define PRIoFAST8 "o"
#define PRIuFAST8 "u"
#define PRIxFAST8 "x"
#define PRIXFAST8 "X"
#define PRIdFAST16 "d"
#define PRIiFAST16 "i"
#define PRIoFAST16 "o"
#define PRIuFAST16 "u"
#define PRIxFAST16 "x"
#define PRIXFAST16 "X"
#define PRIdFAST32 "d"
#define PRIiFAST32 "i"
#define PRIoFAST32 "o"
#define PRIuFAST32 "u"
#define PRIxFAST32 "x"
#define PRIXFAST32 "X"
#define PRIdFAST64 "d"
#define PRIiFAST64 "i"
#define PRIoFAST64 "o"
#define PRIuFAST64 "u"
#define PRIxFAST64 "x"
#define PRIXFAST64 "X"
#define PRIdPTR "d"
#define PRIiPTR "i"
#define PRIoPTR "o"
#define PRIuPTR "u"
#define PRIxPTR "x"
#define PRIXPTR "X"
#define PRIdMAX "d"
#define PRIiMAX "i"
#define PRIoMAX "o"
#define PRIuMAX "u"
#define PRIxMAX "x"
#define PRIXMAX "X"
#define SCNd8 "d"
#define SCNi8 "i"
#define SCNo8 "o"
#define SCNu8 "u"
#define SCNx8 "x"
#define SCNd16 "d"
#define SCNi16 "i"
#define SCNo16 "o"
#define SCNu16 "u"
#define SCNx16 "x"
#define SCNd32 "d"
#define SCNi32 "i"
#define SCNo32 "o"
#define SCNu32 "u"
#define SCNx32 "x"
#define SCNd64 "d"
#define SCNi64 "i"
#define SCNo64 "o"
#define SCNu64 "u"
#define SCNx64 "x"
#define SCNdLEAST8 "d"
#define SCNiLEAST8 "i"
#define SCNoLEAST8 "o"
#define SCNuLEAST8 "u"
#define SCNxLEAST8 "x"
#define SCNdLEAST16 "d"
#define SCNiLEAST16 "i"
#define SCNoLEAST16 "o"
#define SCNuLEAST16 "u"
#define SCNxLEAST16 "x"
#define SCNdLEAST32 "d"
#define SCNiLEAST32 "i"
#define SCNoLEAST32 "o"
#define SCNuLEAST32 "u"
#define SCNxLEAST32 "x"
#define SCNdLEAST64 "d"
#define SCNiLEAST64 "i"
#define SCNoLEAST64 "o"
#define SCNuLEAST64 "u"
#define SCNxLEAST64 "x"
#define SCNdFAST8 "d"
#define SCNiFAST8 "i"
#define SCNoFAST8 "o"
#define SCNuFAST8 "u"
#define SCNxFAST8 "x"
#define SCNdFAST16 "d"
#define SCNiFAST16 "i"
#define SCNoFAST16 "o"
#define SCNuFAST16 "u"
#define SCNxFAST16 "x"
#define SCNdFAST32 "d"
#define SCNiFAST32 "i"
#define SCNoFAST32 "o"
#define SCNuFAST32 "u"
#define SCNxFAST32 "x"
#define SCNdFAST64 "d"
#define SCNiFAST64 "i"
#define SCNoFAST64 "o"
#define SCNuFAST64 "u"
#define SCNxFAST64 "x"
#define SCNdPTR "d"
#define SCNiPTR "i"
#define SCNoPTR "o"
#define SCNuPTR "u"
#define SCNxPTR "x"
#define SCNdMAX "d"
#define SCNiMAX "i"
#define SCNoMAX "o"
#define SCNuMAX "u"
#define SCNxMAX "x"
/* C99 stdbool.h defines */
#define __bool_true_false_are_defined 1
#define false 0
#define true 1
/* va_arg macros and type*/
#define va_start(_ap, _type) __builtin_va_start((_ap))
#define va_arg(_ap, _type) __builtin_va_arg((_ap))
#define va_end(_list)
/* Vectors */
#define __m128 int
#define __m128_u int
#define __m128d int
#define __m128d_u int
#define __m128i int
#define __m128i_u int
#define __m256 int
#define __m256_u int
#define __m256d int
#define __m256d_u int
#define __m256i int
#define __m256i_u int
#define __m512 int
#define __m512_u int
#define __m512d int
#define __m512d_u int
#define __m512i int
#define __m512i_u int
/* C11 stdnoreturn.h defines */
#define __noreturn_is_defined 1
#define noreturn _Noreturn
/* C11 threads.h defines */
#define thread_local _Thread_local
/* C11 assert.h defines */
#define static_assert _Static_assert
/* C11 stdatomic.h defines */
#define ATOMIC_BOOL_LOCK_FREE 0
#define ATOMIC_CHAR_LOCK_FREE 0
#define ATOMIC_CHAR16_T_LOCK_FREE 0
#define ATOMIC_CHAR32_T_LOCK_FREE 0
#define ATOMIC_WCHAR_T_LOCK_FREE 0
#define ATOMIC_SHORT_LOCK_FREE 0
#define ATOMIC_INT_LOCK_FREE 0
#define ATOMIC_LONG_LOCK_FREE 0
#define ATOMIC_LLONG_LOCK_FREE 0
#define ATOMIC_POINTER_LOCK_FREE 0
#define ATOMIC_VAR_INIT(value) (value)
#define ATOMIC_FLAG_INIT { 0 }
#define kill_dependency(y) (y)
/* C11 stdalign.h defines */
#define alignas _Alignas
#define alignof _Alignof
#define __alignas_is_defined 1
#define __alignof_is_defined 1
#endif

View file

@ -0,0 +1,222 @@
#ifndef _FAKE_TYPEDEFS_H
#define _FAKE_TYPEDEFS_H
typedef int size_t;
typedef int __builtin_va_list;
typedef int __gnuc_va_list;
typedef int va_list;
typedef int __int8_t;
typedef int __uint8_t;
typedef int __int16_t;
typedef int __uint16_t;
typedef int __int_least16_t;
typedef int __uint_least16_t;
typedef int __int32_t;
typedef int __uint32_t;
typedef int __int64_t;
typedef int __uint64_t;
typedef int __int_least32_t;
typedef int __uint_least32_t;
typedef int __s8;
typedef int __u8;
typedef int __s16;
typedef int __u16;
typedef int __s32;
typedef int __u32;
typedef int __s64;
typedef int __u64;
typedef int _LOCK_T;
typedef int _LOCK_RECURSIVE_T;
typedef int _off_t;
typedef int __dev_t;
typedef int __uid_t;
typedef int __gid_t;
typedef int _off64_t;
typedef int _fpos_t;
typedef int _ssize_t;
typedef int wint_t;
typedef int _mbstate_t;
typedef int _flock_t;
typedef int _iconv_t;
typedef int __ULong;
typedef int __FILE;
typedef int ptrdiff_t;
typedef int wchar_t;
typedef int char16_t;
typedef int char32_t;
typedef int __off_t;
typedef int __pid_t;
typedef int __loff_t;
typedef int u_char;
typedef int u_short;
typedef int u_int;
typedef int u_long;
typedef int ushort;
typedef int uint;
typedef int clock_t;
typedef int time_t;
typedef int daddr_t;
typedef int caddr_t;
typedef int ino_t;
typedef int off_t;
typedef int dev_t;
typedef int uid_t;
typedef int gid_t;
typedef int pid_t;
typedef int key_t;
typedef int ssize_t;
typedef int mode_t;
typedef int nlink_t;
typedef int fd_mask;
typedef int _types_fd_set;
typedef int clockid_t;
typedef int timer_t;
typedef int useconds_t;
typedef int suseconds_t;
typedef int FILE;
typedef int fpos_t;
typedef int cookie_read_function_t;
typedef int cookie_write_function_t;
typedef int cookie_seek_function_t;
typedef int cookie_close_function_t;
typedef int cookie_io_functions_t;
typedef int div_t;
typedef int ldiv_t;
typedef int lldiv_t;
typedef int sigset_t;
typedef int __sigset_t;
typedef int _sig_func_ptr;
typedef int sig_atomic_t;
typedef int __tzrule_type;
typedef int __tzinfo_type;
typedef int mbstate_t;
typedef int sem_t;
typedef int pthread_t;
typedef int pthread_attr_t;
typedef int pthread_mutex_t;
typedef int pthread_mutexattr_t;
typedef int pthread_cond_t;
typedef int pthread_condattr_t;
typedef int pthread_key_t;
typedef int pthread_once_t;
typedef int pthread_rwlock_t;
typedef int pthread_rwlockattr_t;
typedef int pthread_spinlock_t;
typedef int pthread_barrier_t;
typedef int pthread_barrierattr_t;
typedef int jmp_buf;
typedef int rlim_t;
typedef int sa_family_t;
typedef int sigjmp_buf;
typedef int stack_t;
typedef int siginfo_t;
typedef int z_stream;
/* C99 exact-width integer types */
typedef int int8_t;
typedef int uint8_t;
typedef int int16_t;
typedef int uint16_t;
typedef int int32_t;
typedef int uint32_t;
typedef int int64_t;
typedef int uint64_t;
/* C99 minimum-width integer types */
typedef int int_least8_t;
typedef int uint_least8_t;
typedef int int_least16_t;
typedef int uint_least16_t;
typedef int int_least32_t;
typedef int uint_least32_t;
typedef int int_least64_t;
typedef int uint_least64_t;
/* C99 fastest minimum-width integer types */
typedef int int_fast8_t;
typedef int uint_fast8_t;
typedef int int_fast16_t;
typedef int uint_fast16_t;
typedef int int_fast32_t;
typedef int uint_fast32_t;
typedef int int_fast64_t;
typedef int uint_fast64_t;
/* C99 integer types capable of holding object pointers */
typedef int intptr_t;
typedef int uintptr_t;
/* C99 greatest-width integer types */
typedef int intmax_t;
typedef int uintmax_t;
/* C99 stdbool.h bool type. _Bool is built-in in C99 */
typedef _Bool bool;
/* Mir typedefs */
typedef void* MirEGLNativeWindowType;
typedef void* MirEGLNativeDisplayType;
typedef struct MirConnection MirConnection;
typedef struct MirSurface MirSurface;
typedef struct MirSurfaceSpec MirSurfaceSpec;
typedef struct MirScreencast MirScreencast;
typedef struct MirPromptSession MirPromptSession;
typedef struct MirBufferStream MirBufferStream;
typedef struct MirPersistentId MirPersistentId;
typedef struct MirBlob MirBlob;
typedef struct MirDisplayConfig MirDisplayConfig;
/* xcb typedefs */
typedef struct xcb_connection_t xcb_connection_t;
typedef uint32_t xcb_window_t;
typedef uint32_t xcb_visualid_t;
/* C11 stdatomic.h types */
typedef _Atomic(_Bool) atomic_bool;
typedef _Atomic(char) atomic_char;
typedef _Atomic(signed char) atomic_schar;
typedef _Atomic(unsigned char) atomic_uchar;
typedef _Atomic(short) atomic_short;
typedef _Atomic(unsigned short) atomic_ushort;
typedef _Atomic(int) atomic_int;
typedef _Atomic(unsigned int) atomic_uint;
typedef _Atomic(long) atomic_long;
typedef _Atomic(unsigned long) atomic_ulong;
typedef _Atomic(long long) atomic_llong;
typedef _Atomic(unsigned long long) atomic_ullong;
typedef _Atomic(uint_least16_t) atomic_char16_t;
typedef _Atomic(uint_least32_t) atomic_char32_t;
typedef _Atomic(wchar_t) atomic_wchar_t;
typedef _Atomic(int_least8_t) atomic_int_least8_t;
typedef _Atomic(uint_least8_t) atomic_uint_least8_t;
typedef _Atomic(int_least16_t) atomic_int_least16_t;
typedef _Atomic(uint_least16_t) atomic_uint_least16_t;
typedef _Atomic(int_least32_t) atomic_int_least32_t;
typedef _Atomic(uint_least32_t) atomic_uint_least32_t;
typedef _Atomic(int_least64_t) atomic_int_least64_t;
typedef _Atomic(uint_least64_t) atomic_uint_least64_t;
typedef _Atomic(int_fast8_t) atomic_int_fast8_t;
typedef _Atomic(uint_fast8_t) atomic_uint_fast8_t;
typedef _Atomic(int_fast16_t) atomic_int_fast16_t;
typedef _Atomic(uint_fast16_t) atomic_uint_fast16_t;
typedef _Atomic(int_fast32_t) atomic_int_fast32_t;
typedef _Atomic(uint_fast32_t) atomic_uint_fast32_t;
typedef _Atomic(int_fast64_t) atomic_int_fast64_t;
typedef _Atomic(uint_fast64_t) atomic_uint_fast64_t;
typedef _Atomic(intptr_t) atomic_intptr_t;
typedef _Atomic(uintptr_t) atomic_uintptr_t;
typedef _Atomic(size_t) atomic_size_t;
typedef _Atomic(ptrdiff_t) atomic_ptrdiff_t;
typedef _Atomic(intmax_t) atomic_intmax_t;
typedef _Atomic(uintmax_t) atomic_uintmax_t;
typedef struct atomic_flag { atomic_bool _Value; } atomic_flag;
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
#endif

View file

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View file

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View file

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View file

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View file

@ -0,0 +1,5 @@
#!/usr/bin/bash
REPO_ROOT=$(realpath $(dirname $(dirname $0)))
# Generate odin bindings
python $REPO_ROOT/generator/cli.py $REPO_ROOT/clay.h --output-dir $REPO_ROOT/bindings/odin/clay-odin --generator odin --verbose

View file

View file

@ -0,0 +1,47 @@
from parser import ExtractedSymbols, ExtractedEnum, ExtractedStruct, ExtractedFunction
from typing import Any, Callable, DefaultDict, Literal, NotRequired, Optional, TypedDict
from pathlib import Path
from dataclasses import dataclass
SymbolType = Literal['enum', 'struct', 'function']
class BaseGenerator:
def __init__(self, extracted_symbols: ExtractedSymbols):
self.extracted_symbols = extracted_symbols
self.output_content: dict[str, list[str]] = dict()
def generate(self) -> None:
pass
def has_symbol(self, symbol: str) -> bool:
return (
symbol in self.extracted_symbols.enums or
symbol in self.extracted_symbols.structs or
symbol in self.extracted_symbols.functions
)
def get_symbol_type(self, symbol: str) -> SymbolType:
if symbol in self.extracted_symbols.enums:
return 'enum'
elif symbol in self.extracted_symbols.structs:
return 'struct'
elif symbol in self.extracted_symbols.functions:
return 'function'
raise ValueError(f'Unknown symbol: {symbol}')
def _write(self, file_name: str, content: str) -> None:
if file_name not in self.output_content:
self.output_content[file_name] = []
self.output_content[file_name].append(content)
def write_outputs(self, output_dir: Path) -> None:
for file_name, content in self.output_content.items():
(output_dir / file_name).parent.mkdir(parents=True, exist_ok=True)
with open(output_dir / file_name, 'w') as f:
f.write("\n".join(content))
def get_outputs(self) -> dict[str, str]:
return {file_name: "\n".join(content) for file_name, content in self.output_content.items()}

View file

@ -0,0 +1,164 @@
package clay
import "core:c"
import "core:strings"
when ODIN_OS == .Windows {
foreign import Clay "windows/clay.lib"
} else when ODIN_OS == .Linux {
foreign import Clay "linux/clay.a"
} else when ODIN_OS == .Darwin {
when ODIN_ARCH == .arm64 {
foreign import Clay "macos-arm64/clay.a"
} else {
foreign import Clay "macos/clay.a"
}
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
foreign import Clay "wasm/clay.o"
}
when ODIN_OS == .Windows {
EnumBackingType :: u32
} else {
EnumBackingType :: u8
}
{{enums}}
Context :: struct {
}
ClayArray :: struct($type: typeid) {
capacity: i32,
length: i32,
internalArray: [^]type,
}
SizingConstraints :: struct #raw_union {
sizeMinMax: SizingConstraintsMinMax,
sizePercent: c.float,
}
TypedConfig :: struct {
type: ElementConfigType,
config: rawptr,
id: ElementId,
}
{{structs}}
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
{{public_functions}}
}
@(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay {
{{private_functions}}
}
@(require_results, deferred_none = _CloseElement)
UI :: proc(configs: ..TypedConfig) -> bool {
_OpenElement()
for config in configs {
#partial switch (config.type) {
case ElementConfigType.Id:
_AttachId(config.id)
case ElementConfigType.Layout:
_AttachLayoutConfig(cast(^LayoutConfig)config.config)
case:
_AttachElementConfig(config.config, config.type)
}
}
_ElementPostConfiguration()
return true
}
Layout :: proc(config: LayoutConfig) -> TypedConfig {
return {type = ElementConfigType.Layout, config = _StoreLayoutConfig(config) }
}
PaddingAll :: proc (padding: u16) -> Padding {
return { padding, padding, padding, padding }
}
Rectangle :: proc(config: RectangleElementConfig) -> TypedConfig {
return {type = ElementConfigType.Rectangle, config = _StoreRectangleElementConfig(config)}
}
Text :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config)
}
TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig {
return _StoreTextElementConfig(config)
}
Image :: proc(config: ImageElementConfig) -> TypedConfig {
return {type = ElementConfigType.Image, config = _StoreImageElementConfig(config)}
}
Floating :: proc(config: FloatingElementConfig) -> TypedConfig {
return {type = ElementConfigType.Floating, config = _StoreFloatingElementConfig(config)}
}
Custom :: proc(config: CustomElementConfig) -> TypedConfig {
return {type = ElementConfigType.Custom, config = _StoreCustomElementConfig(config)}
}
Scroll :: proc(config: ScrollElementConfig) -> TypedConfig {
return {type = ElementConfigType.Scroll, config = _StoreScrollElementConfig(config)}
}
Border :: proc(config: BorderElementConfig) -> TypedConfig {
return {type = ElementConfigType.Border, config = _StoreBorderElementConfig(config)}
}
BorderOutside :: proc(outsideBorders: BorderData) -> TypedConfig {
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders}) }
}
BorderOutsideRadius :: proc(outsideBorders: BorderData, radius: f32) -> TypedConfig {
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig(
(BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}},
) }
}
BorderAll :: proc(allBorders: BorderData) -> TypedConfig {
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders}) }
}
BorderAllRadius :: proc(allBorders: BorderData, radius: f32) -> TypedConfig {
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig(
(BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}},
) }
}
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
return CornerRadius{radius, radius, radius, radius}
}
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
return SizingAxis{type = SizingType.FIT, constraints = {sizeMinMax = sizeMinMax}}
}
SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
return SizingAxis{type = SizingType.GROW, constraints = {sizeMinMax = sizeMinMax}}
}
SizingFixed :: proc(size: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.FIXED, constraints = {sizeMinMax = {size, size}}}
}
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.PERCENT, constraints = {sizePercent = sizePercent}}
}
MakeString :: proc(label: string) -> String {
return String{chars = raw_data(label), length = cast(c.int)len(label)}
}
ID :: proc(label: string, index: u32 = 0) -> TypedConfig {
return { type = ElementConfigType.Id, id = _HashString(MakeString(label), index, 0) }
}

View file

@ -0,0 +1,315 @@
from pathlib import Path
import logging
from parser import ExtractedSymbolType
from generators.base_generator import BaseGenerator
logger = logging.getLogger(__name__)
def get_common_prefix(keys: list[str]) -> str:
# find a prefix that's shared between all keys
prefix = ""
for i in range(min(map(len, keys))):
if len(set(key[i] for key in keys)) > 1:
break
prefix += keys[0][i]
return prefix
def snake_case_to_pascal_case(snake_case: str) -> str:
return ''.join(word.lower().capitalize() for word in snake_case.split('_'))
SYMBOL_NAME_OVERRIDES = {
'Clay_TextElementConfigWrapMode': 'TextWrapMode',
'Clay_Border': 'BorderData',
'Clay_SizingMinMax': 'SizingConstraintsMinMax',
}
SYMBOL_COMPLETE_OVERRIDES = {
'Clay_RenderCommandArray': 'ClayArray(RenderCommand)',
'Clay_Context': 'Context',
'Clay_ElementConfig': None,
# 'Clay_SetQueryScrollOffsetFunction': None,
}
# These enums should have output binding members that are PascalCase instead of UPPER_SNAKE_CASE.
ENUM_MEMBER_PASCAL = {
'Clay_RenderCommandType',
'Clay_TextElementConfigWrapMode',
'Clay__ElementConfigType',
}
ENUM_MEMBER_OVERRIDES = {
'Clay__ElementConfigType': {
'CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER': 'Border',
'CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER': 'Floating',
'CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER': 'Scroll',
}
}
ENUM_ADDITIONAL_MEMBERS = {
'Clay__ElementConfigType': {
'Id': 65,
'Layout': 66,
}
}
TYPE_MAPPING = {
'*char': '[^]c.char',
'const *char': '[^]c.char',
'*void': 'rawptr',
'bool': 'bool',
'float': 'c.float',
'uint16_t': 'u16',
'uint32_t': 'u32',
'int32_t': 'c.int32_t',
'uintptr_t': 'rawptr',
'intptr_t': 'rawptr',
'void': 'void',
}
STRUCT_TYPE_OVERRIDES = {
'Clay_Arena': {
'nextAllocation': 'uintptr',
'capacity': 'uintptr',
},
'Clay_SizingAxis': {
'size': 'SizingConstraints',
},
"Clay_RenderCommand": {
"zIndex": 'i32',
},
}
STRUCT_MEMBER_OVERRIDES = {
'Clay_ErrorHandler': {
'errorHandlerFunction': 'handler',
},
'Clay_SizingAxis': {
'size': 'constraints',
},
}
STRUCT_OVERRIDE_AS_FIXED_ARRAY = {
'Clay_Color',
'Clay_Vector2',
}
FUNCTION_PARAM_OVERRIDES = {
'Clay_SetCurrentContext': {
'context': 'ctx',
},
}
FUNCTION_TYPE_OVERRIDES = {
'Clay_CreateArenaWithCapacityAndMemory': {
'offset': '[^]u8',
},
'Clay_SetMeasureTextFunction': {
'userData': 'uintptr',
},
'Clay_RenderCommandArray_Get': {
'index': 'i32',
},
"Clay__AttachElementConfig": {
"config": 'rawptr',
},
}
class OdinGenerator(BaseGenerator):
def generate(self) -> None:
self.generate_structs()
self.generate_enums()
self.generate_functions()
odin_template_path = Path(__file__).parent / 'odin' / 'clay.template.odin'
with open(odin_template_path, 'r') as f:
template = f.read()
self.output_content['clay.odin'] = (
template
.replace('{{structs}}', '\n'.join(self.output_content['struct']))
.replace('{{enums}}', '\n'.join(self.output_content['enum']))
.replace('{{public_functions}}', '\n'.join(self.output_content['public_function']))
.replace('{{private_functions}}', '\n'.join(self.output_content['private_function']))
.splitlines()
)
del self.output_content['struct']
del self.output_content['enum']
del self.output_content['private_function']
del self.output_content['public_function']
def get_symbol_name(self, symbol: str) -> str:
if symbol in SYMBOL_NAME_OVERRIDES:
return SYMBOL_NAME_OVERRIDES[symbol]
symbol_type = self.get_symbol_type(symbol)
base_name = symbol.removeprefix('Clay_')
if symbol_type == 'enum':
return base_name.removeprefix('_') # Clay_ and Clay__ are exported as public types.
elif symbol_type == 'struct':
return base_name
elif symbol_type == 'function':
return base_name
raise ValueError(f'Unknown symbol: {symbol}')
def format_type(self, type: ExtractedSymbolType) -> str:
if isinstance(type, str):
return type
parameter_strs = []
for param_name, param_type in type['params']:
parameter_strs.append(f"{param_name}: {self.format_type(param_type or 'unknown')}")
return_type_str = ''
if type['return_type'] is not None and type['return_type'] != 'void':
return_type_str = ' -> ' + self.format_type(type['return_type'])
return f"proc \"c\" ({', '.join(parameter_strs)}){return_type_str}"
def resolve_binding_type(self, symbol: str, member: str | None, member_type: ExtractedSymbolType | None, type_overrides: dict[str, dict[str, str]]) -> str | None:
if isinstance(member_type, str):
if member_type in SYMBOL_COMPLETE_OVERRIDES:
return SYMBOL_COMPLETE_OVERRIDES[member_type]
if symbol in type_overrides and member in type_overrides[symbol]:
return type_overrides[symbol][member]
if member_type in TYPE_MAPPING:
return TYPE_MAPPING[member_type]
if member_type and self.has_symbol(member_type):
return self.get_symbol_name(member_type)
if member_type and member_type.startswith('*'):
result = self.resolve_binding_type(symbol, member, member_type[1:], type_overrides)
if result:
return f"^{result}"
return None
if member_type is None:
return None
resolved_parameters = []
for param_name, param_type in member_type['params']:
resolved_param = self.resolve_binding_type(symbol, param_name, param_type, type_overrides)
if resolved_param is None:
return None
resolved_parameters.append((param_name, resolved_param))
resolved_return_type = self.resolve_binding_type(symbol, None, member_type['return_type'], type_overrides)
if resolved_return_type is None:
return None
return self.format_type({
"params": resolved_parameters,
"return_type": resolved_return_type,
})
def generate_structs(self) -> None:
for struct, struct_data in sorted(self.extracted_symbols.structs.items(), key=lambda x: x[0]):
members = struct_data['attrs']
if not struct.startswith('Clay_'):
continue
if struct in SYMBOL_COMPLETE_OVERRIDES:
continue
binding_name = self.get_symbol_name(struct)
if binding_name.startswith('_'):
continue
if struct in STRUCT_OVERRIDE_AS_FIXED_ARRAY:
array_size = len(members)
first_elem = list(members.values())[0]
array_type = None
if 'type' in first_elem:
array_type = first_elem['type']
if array_type in TYPE_MAPPING:
array_binding_type = TYPE_MAPPING[array_type]
elif array_type and self.has_symbol(self.format_type(array_type)):
array_binding_type = self.get_symbol_name(self.format_type(array_type))
else:
self._write('struct', f"// {struct} ({array_type}) - has no mapping")
continue
self._write('struct', f"// {struct} (overridden as fixed array)")
self._write('struct', f"{binding_name} :: [{array_size}]{array_binding_type}")
self._write('struct', "")
continue
raw_union = ' #raw_union' if struct_data.get('is_union', False) else ''
self._write('struct', f"// {struct}")
self._write('struct', f"{binding_name} :: struct{raw_union} {{")
for member, member_info in members.items():
if struct in STRUCT_TYPE_OVERRIDES and member in STRUCT_TYPE_OVERRIDES[struct]:
member_type = 'unknown'
elif not 'type' in member_info:
self._write('struct', f" // {member} (unknown type)")
continue
else:
member_type = member_info['type']
binding_member_name = member
if struct in STRUCT_MEMBER_OVERRIDES and member in STRUCT_MEMBER_OVERRIDES[struct]:
binding_member_name = STRUCT_MEMBER_OVERRIDES[struct][member]
member_binding_type = self.resolve_binding_type(struct, member, member_type, STRUCT_TYPE_OVERRIDES)
if member_binding_type is None:
self._write('struct', f" // {binding_member_name} ({member_type}) - has no mapping")
continue
self._write('struct', f" {binding_member_name}: {member_binding_type}, // {member} ({member_type})")
self._write('struct', "}")
self._write('struct', '')
def generate_enums(self) -> None:
for enum, members in sorted(self.extracted_symbols.enums.items(), key=lambda x: x[0]):
if not enum.startswith('Clay_'):
continue
if enum in SYMBOL_COMPLETE_OVERRIDES:
continue
binding_name = self.get_symbol_name(enum)
common_member_prefix = get_common_prefix(list(members.keys()))
self._write('enum', f"// {enum}")
self._write('enum', f"{binding_name} :: enum EnumBackingType {{")
for member in members:
if enum in ENUM_MEMBER_OVERRIDES and member in ENUM_MEMBER_OVERRIDES[enum]:
binding_member_name = ENUM_MEMBER_OVERRIDES[enum][member]
else:
binding_member_name = member.removeprefix(common_member_prefix)
if enum in ENUM_MEMBER_PASCAL:
binding_member_name = snake_case_to_pascal_case(binding_member_name)
if members[member] is not None:
self._write('enum', f" {binding_member_name} = {members[member]}, // {member}")
else:
self._write('enum', f" {binding_member_name}, // {member}")
if enum in ENUM_ADDITIONAL_MEMBERS:
self._write('enum', ' // Odin specific enum types')
for member, value in ENUM_ADDITIONAL_MEMBERS[enum].items():
self._write('enum', f" {member} = {value},")
self._write('enum', "}")
self._write('enum', '')
def generate_functions(self) -> None:
for function, function_info in sorted(self.extracted_symbols.functions.items(), key=lambda x: x[0]):
if not function.startswith('Clay_'):
continue
if function in SYMBOL_COMPLETE_OVERRIDES:
continue
is_private = function.startswith('Clay__')
write_to = 'private_function' if is_private else 'public_function'
binding_name = self.get_symbol_name(function)
return_type = function_info['return_type']
binding_return_type = self.resolve_binding_type(function, None, return_type, {})
if binding_return_type is None:
self._write(write_to, f" // {function} ({return_type}) - has no mapping")
continue
skip = False
binding_params = []
for param_name, param_type in function_info['params']:
binding_param_name = param_name
if function in FUNCTION_PARAM_OVERRIDES and param_name in FUNCTION_PARAM_OVERRIDES[function]:
binding_param_name = FUNCTION_PARAM_OVERRIDES[function][param_name]
binding_param_type = self.resolve_binding_type(function, param_name, param_type, FUNCTION_TYPE_OVERRIDES)
if binding_param_type is None:
skip = True
binding_params.append(f"{binding_param_name}: {binding_param_type}")
if skip:
self._write(write_to, f" // {function} - has no mapping")
continue
binding_params_str = ', '.join(binding_params)
return_str = f" -> {binding_return_type}" if binding_return_type != 'void' else ''
self._write(write_to, f" {binding_name} :: proc({binding_params_str}){return_str} --- // {function}")

173
generator/parser.py Normal file
View file

@ -0,0 +1,173 @@
from dataclasses import dataclass
from typing import Optional, TypedDict, NotRequired, Union
from pycparser import c_ast, parse_file, preprocess_file
from pathlib import Path
import os
import json
import shutil
import logging
logger = logging.getLogger(__name__)
ExtractedSymbolType = Union[str, "ExtractedFunction"]
class ExtractedStructAttributeUnion(TypedDict):
type: Optional[ExtractedSymbolType]
class ExtractedStructAttribute(TypedDict):
type: NotRequired[ExtractedSymbolType]
union: NotRequired[dict[str, Optional[ExtractedSymbolType]]]
class ExtractedStruct(TypedDict):
attrs: dict[str, ExtractedStructAttribute]
is_union: NotRequired[bool]
ExtractedEnum = dict[str, Optional[str]]
ExtractedFunctionParam = tuple[str, Optional[ExtractedSymbolType]]
class ExtractedFunction(TypedDict):
return_type: Optional["ExtractedSymbolType"]
params: list[ExtractedFunctionParam]
@dataclass
class ExtractedSymbols:
structs: dict[str, ExtractedStruct]
enums: dict[str, ExtractedEnum]
functions: dict[str, ExtractedFunction]
def get_type_names(node: c_ast.Node, prefix: str="") -> Optional[ExtractedSymbolType]:
if isinstance(node, c_ast.TypeDecl) and hasattr(node, 'quals') and node.quals:
prefix = " ".join(node.quals) + " " + prefix
if isinstance(node, c_ast.PtrDecl):
prefix = "*" + prefix
if isinstance(node, c_ast.FuncDecl):
func: ExtractedFunction = {
'return_type': get_type_names(node.type),
'params': [],
}
for param in node.args.params:
if param.name is None:
continue
func['params'].append((param.name, get_type_names(param)))
return func
if hasattr(node, 'names'):
return prefix + node.names[0] # type: ignore
elif hasattr(node, 'type'):
return get_type_names(node.type, prefix) # type: ignore
return None
class Visitor(c_ast.NodeVisitor):
def __init__(self):
self.structs: dict[str, ExtractedStruct] = {}
self.enums: dict[str, ExtractedEnum] = {}
self.functions: dict[str, ExtractedFunction] = {}
def visit_FuncDecl(self, node: c_ast.FuncDecl):
# node.show()
# logger.debug(node)
node_type = node.type
is_pointer = False
if isinstance(node.type, c_ast.PtrDecl):
node_type = node.type.type
is_pointer = True
if hasattr(node_type, "declname"):
return_type = get_type_names(node_type.type)
if return_type is not None and isinstance(return_type, str) and is_pointer:
return_type = "*" + return_type
func: ExtractedFunction = {
'return_type': return_type,
'params': [],
}
for param in node.args.params:
if param.name is None:
continue
func['params'].append((param.name, get_type_names(param)))
self.functions[node_type.declname] = func
self.generic_visit(node)
def visit_Struct(self, node: c_ast.Struct):
# node.show()
if node.name and node.decls:
struct = {}
for decl in node.decls:
struct[decl.name] = {
"type": get_type_names(decl),
}
self.structs[node.name] = {
'attrs': struct,
}
self.generic_visit(node)
def visit_Typedef(self, node: c_ast.Typedef):
# node.show()
if hasattr(node.type, 'type') and hasattr(node.type.type, 'decls') and node.type.type.decls:
struct = {}
for decl in node.type.type.decls:
if hasattr(decl, 'type') and hasattr(decl.type, 'type') and isinstance(decl.type.type, c_ast.Union):
union = {}
for field in decl.type.type.decls:
union[field.name] = get_type_names(field)
struct[decl.name] = {
'union': union
}
else:
struct[decl.name] = {
"type": get_type_names(decl),
}
self.structs[node.name] = {
'attrs': struct,
'is_union': isinstance(node.type.type, c_ast.Union),
}
if hasattr(node.type, 'type') and isinstance(node.type.type, c_ast.Enum):
enum = {}
for enumerator in node.type.type.values.enumerators:
if enumerator.value is None:
enum[enumerator.name] = None
else:
enum[enumerator.name] = enumerator.value.value
self.enums[node.name] = enum
self.generic_visit(node)
def parse_headers(input_files: list[Path], tmp_dir: Path) -> ExtractedSymbols:
cpp_args = ["-nostdinc", "-D__attribute__(x)=", "-E"]
# Make a new clay.h that combines the provided input files, so that we can add bindings for customized structs
with open(tmp_dir / 'merged_clay.h', 'w') as f:
for input_file in input_files:
with open(input_file, 'r') as f2:
for line in f2:
# Ignore includes, as they should be manually included in input_files.
if line.startswith("#include"):
continue
# Ignore the CLAY_IMPLEMENTATION define, because we only want to parse the public api code.
# This is helpful so that the user can provide their implementation code, which will contain any custom extensions
if "#define CLAY_IMPLEMENTATION" in line:
continue
f.write(line)
# Preprocess the file
logger.info("Preprocessing file")
preprocessed = preprocess_file(tmp_dir / 'merged_clay.h', cpp_path="cpp", cpp_args=cpp_args) # type: ignore
with open(tmp_dir / 'clay.preprocessed.h', 'w') as f:
f.write(preprocessed)
# Parse the file
logger.info("Parsing file")
ast = parse_file(tmp_dir / 'clay.preprocessed.h', use_cpp=False) # type: ignore
# Extract symbols
visitor = Visitor()
visitor.visit(ast)
result = ExtractedSymbols(
structs=visitor.structs,
enums=visitor.enums,
functions=visitor.functions
)
return result

View file

@ -0,0 +1 @@
pycparser==2.22

View file

@ -3,6 +3,8 @@
Copyright (c) 2025 Mivirl
altered by Godje (Sep 2025)
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.
@ -1616,6 +1618,20 @@ void Clay_Termbox_Render(Clay_RenderCommandArray commands)
Clay_StringSlice *text = &render_data.stringContents;
int32_t i = 0;
// culling text characters that are outside of the layout
int h_clip = 0 - cell_box.x;
while(h_clip > 0 && i < text->length){
uint32_t ch = ' ';
int codepoint_length = tb_utf8_char_to_unicode(&ch, text->chars + i);
if (0 > codepoint_length) {
clay_tb_assert(false, "Invalid utf8");
}
i += codepoint_length;
h_clip -= 1;
}
// printing the rest of the characters
for (int y = box_begin_y; y < box_end_y; ++y) {
for (int x = box_begin_x; x < box_end_x;) {
uint32_t ch = ' ';