Compare commits

..

1 Commits

Author SHA1 Message Date
Michael Savage
fb7c2dd628
Merge 977ef87b92 into 766325c395 2025-02-20 09:29:24 +13:00
2 changed files with 71 additions and 130 deletions

View File

@ -1,6 +1,6 @@
### Odin Language Bindings ### Odin Language Bindings
This directory contains bindings for the [Odin](odin-lang.org) programming language, as well as an example implementation of the [Clay website](https://nicbarker.com/clay) in Odin. This directory contains bindings for the [Odin](odin-lang.org) programming language, as well as an example implementation of the [clay website](https://nicbarker.com/clay) in Odin.
Special thanks to Special thanks to
@ -8,21 +8,21 @@ Special thanks to
- [Dudejoe870](https://github.com/Dudejoe870) - [Dudejoe870](https://github.com/Dudejoe870)
- MrStevns from the Odin Discord server - MrStevns from the Odin Discord server
If you haven't taken a look at the [full documentation for Clay](https://github.com/nicbarker/clay/blob/main/README.md), it's recommended that you take a look there first to familiarise yourself with the general concepts. This README is abbreviated and applies to using Clay in Odin specifically. If you haven't taken a look at the [full documentation for clay](https://github.com/nicbarker/clay/blob/main/README.md), it's recommended that you take a look there first to familiarise yourself with the general concepts. This README is abbreviated and applies to using clay in Odin specifically.
The **most notable difference** between the C API and the Odin bindings is the use of if statements to create the scope for declaring child elements. When using the equivalent of the [Element Macros](https://github.com/nicbarker/clay/blob/main/README.md#element-macros):
The **most notable difference** between the C API and the Odin bindings is the use of `if` statements to create the scope for declaring child elements, when using the equivalent of the [Element Macros](https://github.com/nicbarker/clay/blob/main/README.md#element-macros):
```C ```C
// C form of element macros // C form of element macros
// Define an element with 16px of x and y padding CLAY_RECTANGLE(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {
CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) { // Child elements here
// Child elements here });
}
``` ```
```Odin ```Odin
// Odin form of element macros // Odin form of element macros
if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) { if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BOTTOM, sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, padding = {16, 16} }), clay.RectangleConfig({ color = COLOR_LIGHT })) {
// Child elements here // Child elements here
} }
``` ```
@ -34,170 +34,111 @@ if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }
import clay "clay-odin" import clay "clay-odin"
``` ```
2. Ask Clay for how much static memory it needs using [clay.MinMemorySize()](https://github.com/nicbarker/clay/blob/main/README.md#clay_minmemorysize), create an Arena for it to use with [clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)](https://github.com/nicbarker/clay/blob/main/README.md#clay_createarenawithcapacityandmemory), and initialize it with [clay.Initialize(clay.Arena, clay.Dimensions, clay.ErrorHandler)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize). 2. Ask clay for how much static memory it needs using [clay.MinMemorySize()](https://github.com/nicbarker/clay/blob/main/README.md#clay_minmemorysize), create an Arena for it to use with [clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)](https://github.com/nicbarker/clay/blob/main/README.md#clay_createarenawithcapacityandmemory), and initialize it with [clay.Initialize(arena)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
```Odin ```Odin
error_handler :: proc "c" (errorData: clay.ErrorData) { minMemorySize: u32 = clay.MinMemorySize()
// Do something with the error data. memory := make([^]u8, minMemorySize)
} arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.Initialize(arena)
min_memory_size: u32 = clay.MinMemorySize()
memory := make([^]u8, min_memory_size)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(min_memory_size, memory)
clay.Initialize(arena, { width = 1080, height = 720 }, { handler = error_handler })
``` ```
3. Provide a `measure_text(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that Clay can measure and wrap text. 3. Provide a `measureText(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that clay can measure and wrap text.
```Odin ```Odin
// Example measure text function // Example measure text function
measure_text :: proc "c" ( measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions {
text: clay.StringSlice, // clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
config: ^clay.TextElementConfig,
userData: rawptr,
) -> clay.Dimensions {
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing, etc..
// Note: clay.String->chars is not guaranteed to be null terminated // Note: clay.String->chars is not guaranteed to be null terminated
return {
width = f32(text.length * i32(config.fontSize)),
height = f32(config.fontSize),
}
} }
// Tell clay how to measure text // Tell clay how to measure text
clay.SetMeasureTextFunction(measure_text, nil) clay.SetMeasureTextFunction(measureText)
``` ```
4. **Optional** - Call [clay.SetPointerState(pointerPosition, isPointerDown)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerstate) if you want to use mouse interactions. 4. **Optional** - Call [clay.SetPointerPosition(pointerPosition)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerposition) if you want to use mouse interactions.
```Odin ```Odin
// Update internal pointer position for handling mouseover / click / touch events // Update internal pointer position for handling mouseover / click / touch events
clay.SetPointerState( clay.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY })
clay.Vector2 { mouse_pos_x, mouse_pos_y },
is_mouse_down,
)
``` ```
5. Call [clay.BeginLayout()](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros. 5. Call [clay.BeginLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros.
```Odin ```Odin
// Define some colors.
COLOR_LIGHT :: clay.Color{224, 215, 210, 255} COLOR_LIGHT :: clay.Color{224, 215, 210, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255} COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 255} COLOR_ORANGE :: clay.Color{225, 138, 50, 255}
COLOR_BLACK :: clay.Color{0, 0, 0, 255}
// Layout config is just a struct that can be declared statically, or inline // Layout config is just a struct that can be declared statically, or inline
sidebar_item_layout := clay.LayoutConfig { sidebarItemLayout := clay.LayoutConfig {
sizing = { sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)},
width = clay.SizingGrow({}),
height = clay.SizingFixed(50)
},
} }
// Re-useable components are just normal procs. // Re-useable components are just normal functions
sidebar_item_component :: proc(index: u32) { SidebarItemComponent :: proc(index: u32) {
if clay.UI()({ if clay.Rectangle(clay.ID("SidebarBlob", index), &sidebarItemLayout, clay.RectangleConfig({color = COLOR_ORANGE})) {}
id = clay.ID("SidebarBlob", index),
layout = sidebar_item_layout,
backgroundColor = COLOR_ORANGE,
}) {}
} }
// An example function to create your layout tree // An example function to begin the "root" of your layout tree
create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) { CreateLayout :: proc() -> clay.ClayArray(clay.RenderCommand) {
// Begin constructing the layout. clay.BeginLayout(windowWidth, windowHeight)
clay.BeginLayout()
// An example of laying out a UI with a fixed-width sidebar and flexible-width main content // An example of laying out a UI with a fixed width sidebar and flexible width main content
// NOTE: To create a scope for child components, the Odin API uses `if` with components that have children // NOTE: To create a scope for child components, the Odin api uses `if` with components that have children
if clay.UI()({ if clay.Rectangle(
id = clay.ID("OuterContainer"), clay.ID("OuterContainer"),
layout = { clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) }, clay.RectangleConfig({color = {250, 250, 255, 255}}),
padding = { 16, 16, 16, 16 }, ) {
childGap = 16, if clay.Rectangle(
}, clay.ID("SideBar"),
backgroundColor = { 250, 250, 255, 255 }, clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingFixed(300), height = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
}) { clay.RectangleConfig({color = COLOR_LIGHT}),
if clay.UI()({ ) {
id = clay.ID("SideBar"), if clay.Rectangle(
layout = { clay.ID("ProfilePictureOuter"),
layoutDirection = .TopToBottom, clay.Layout({sizing = {width = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16, childAlignment = {y = .CENTER}}),
sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, clay.RectangleConfig({color = COLOR_RED}),
padding = { 16, 16, 16, 16 }, ) {
childGap = 16, if clay.Image(
}, clay.ID("ProfilePicture"),
backgroundColor = COLOR_LIGHT, clay.Layout({sizing = {width = clay.SizingFixed(60), height = clay.SizingFixed(60)}}),
}) { clay.ImageConfig({imageData = &profilePicture, sourceDimensions = {height = 60, width = 60}}),
if clay.UI()({ ) {}
id = clay.ID("ProfilePictureOuter"), clay.Text(clay.ID("ProfileTitle"), "Clay - UI Library", clay.TextConfig({fontSize = 24, textColor = {255, 255, 255, 255}}))
layout = {
sizing = { width = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
childAlignment = { y = .Center },
},
backgroundColor = COLOR_RED,
cornerRadius = { 6, 6, 6, 6 },
}) {
if clay.UI()({
id = clay.ID("ProfilePicture"),
layout = {
sizing = { width = clay.SizingFixed(60), height = clay.SizingFixed(60) },
},
image = {
imageData = &profile_picture,
sourceDimensions = {
width = 60,
height = 60,
},
},
}) {}
clay.Text(
"Clay - UI Library",
clay.TextConfig({ textColor = COLOR_BLACK, fontSize = 16 }),
)
} }
// Standard Odin code like loops, etc. work inside components. // Standard Odin code like loops etc work inside components
// Here we render 5 sidebar items. for i in 0..<10 {
for i in u32(0)..<5 { SidebarItemComponent(i)
sidebar_item_component(i)
} }
} }
if clay.UI()({ if clay.Rectangle(
id = clay.ID("MainContent"), clay.ID("MainContent"),
layout = { clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}),
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) }, clay.RectangleConfig({color = COLOR_LIGHT}),
}, ) {}
backgroundColor = COLOR_LIGHT,
}) {}
} }
// ...
// Returns a list of render commands
render_commands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout()
return render_commands
} }
``` ```
6. Call your layout proc and process the resulting [clay.ClayArray(clay.RenderCommand)](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer. 6. Call [clay.EndLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_endlayout) and process the resulting [clay.RenderCommandArray](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer.
```Odin ```Odin
render_commands := create_layout() renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight)
for i in 0..<i32(render_commands.length) { for i: u32 = 0; i < renderCommands.length; i += 1 {
render_command := clay.RenderCommandArray_Get(render_commands, i) renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i)
switch render_command.commandType { switch renderCommand.commandType {
case .Rectangle: case .Rectangle:
DrawRectangle(render_command.boundingBox, render_command.config.rectangleElementConfig.color) DrawRectangle(renderCommand.boundingBox, renderCommand.config.rectangleElementConfig.color)
// ... Implement handling of other command types // ... Implement handling of other command types
} }
} }
``` ```
Please see the [full C documentation for Clay](https://github.com/nicbarker/clay/blob/main/README.md) for API details. All public C functions and Macros have Odin binding equivalents, generally of the form `CLAY_ID` (C) -> `clay.ID` (Odin). Please see the [full C documentation for clay](https://github.com/nicbarker/clay/blob/main/README.md) for API details. All public C functions and Macros have Odin binding equivalents, generally of the form `CLAY_RECTANGLE` (C) -> `clay.Rectangle` (Odin)

4
clay.h
View File

@ -339,8 +339,6 @@ typedef CLAY_PACKED_ENUM {
// Controls various functionality related to text elements. // Controls various functionality related to text elements.
typedef struct { typedef struct {
// A pointer that will be transparently passed through to the resulting render command.
void *userData;
// The RGBA color of the font to render, conventionally specified as 0-255. // The RGBA color of the font to render, conventionally specified as 0-255.
Clay_Color textColor; Clay_Color textColor;
// An integer transparently passed to Clay_MeasureText to identify the font to use. // An integer transparently passed to Clay_MeasureText to identify the font to use.
@ -362,6 +360,8 @@ typedef struct {
// CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box. // CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box.
// CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box. // CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
Clay_TextAlignment textAlignment; Clay_TextAlignment textAlignment;
// A pointer that will be transparently passed through to the resulting render command.
void *userData;
// When set to true, clay will hash the entire text contents of this string as an identifier for its internal // When set to true, clay will hash the entire text contents of this string as an identifier for its internal
// text measurement cache, rather than just the pointer and length. This will incur significant performance cost for // text measurement cache, rather than just the pointer and length. This will incur significant performance cost for
// long bodies of text. // long bodies of text.