Update odin bindings and html renderer

This commit is contained in:
Nic Barker 2024-09-10 21:24:40 +12:00
parent a79b6a9faf
commit 06fd9264ac
15 changed files with 195 additions and 276 deletions

View file

@ -1,144 +0,0 @@
### 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.
Special thanks to
- [laytan](https://github.com/laytan)
- [Dudejoe870](https://github.com/Dudejoe870)
- 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.
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 form of element macros
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), {
// Child elements here
});
```
```Odin
// Odin form of element macros
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
}
```
### Quick Start
1. Download the [clay-odin](https://github.com/nicbarker/clay/tree/main/bindings/odin/clay-odin) directory and copy it into your project.
```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(arena)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
```Odin
minMemorySize: u32 = clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.Initialize(arena)
```
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
// Example measure text function
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions {
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
// Note: clay.String->chars is not guaranteed to be null terminated
}
// Tell clay how to measure text
clay.SetMeasureTextFunction(measureText)
```
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
// Update internal pointer position for handling mouseover / click / touch events
clay.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY })
```
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
COLOR_LIGHT :: clay.Color{224, 215, 210, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 255}
// Layout config is just a struct that can be declared statically, or inline
sidebarItemLayout := clay.LayoutConfig {
sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)},
}
// Re-useable components are just normal functions
SidebarItemComponent :: proc(index: u32) {
if clay.Rectangle(clay.ID("SidebarBlob", index), &sidebarItemLayout, clay.RectangleConfig({color = COLOR_ORANGE})) {}
}
// An example function to begin the "root" of your layout tree
CreateLayout :: proc() -> clay.ClayArray(clay.RenderCommand) {
clay.BeginLayout(windowWidth, windowHeight)
// 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
if clay.Rectangle(
clay.ID("OuterContainer"),
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
clay.RectangleConfig({color = {250, 250, 255, 255}}),
) {
if clay.Rectangle(
clay.ID("SideBar"),
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.Rectangle(
clay.ID("ProfilePictureOuter"),
clay.Layout({sizing = {width = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16, childAlignment = {y = .CENTER}}),
clay.RectangleConfig({color = COLOR_RED}),
) {
if clay.Image(
clay.ID("ProfilePicture"),
clay.Layout({sizing = {width = clay.SizingFixed(60), height = clay.SizingFixed(60)}}),
clay.ImageConfig({imageData = &profilePicture, sourceDimensions = {height = 60, width = 60}}),
) {}
clay.Text(clay.ID("ProfileTitle"), "Clay - UI Library", clay.TextConfig({fontSize = 24, textColor = {255, 255, 255, 255}}))
}
// Standard Odin code like loops etc work inside components
for i in 0..<10 {
SidebarItemComponent(i)
}
}
if clay.Rectangle(
clay.ID("MainContent"),
clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}),
clay.RectangleConfig({color = COLOR_LIGHT}),
) {}
}
// ...
}
```
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
renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight)
for i: u32 = 0; i < renderCommands.length; i += 1 {
renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i)
switch renderCommand.commandType {
case .Rectangle:
DrawRectangle(renderCommand.boundingBox, renderCommand.config.rectangleElementConfig.color)
// ... 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_RECTANGLE` (C) -> `clay.Rectangle` (Odin)

View file

@ -86,12 +86,19 @@ RectangleElementConfig :: struct {
cornerRadius: CornerRadius,
}
TextWrapMode :: enum EnumBackingType {
Words,
Newlines,
None,
}
TextElementConfig :: struct {
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineSpacing: u16,
wrapMode: TextWrapMode,
}
ImageElementConfig :: struct {
@ -242,14 +249,16 @@ foreign Clay {
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
Initialize :: proc(arena: Arena) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions) ---
UpdateScrollContainers :: proc(isPointerActive: bool, scrollDelta: Vector2, deltaTime: c.float) ---
BeginLayout :: proc(screenWidth: c.int, screenHeight: c.int) ---
EndLayout :: proc(screenWidth: c.int, screenHeight: c.int) -> ClayArray(RenderCommand) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---
EndLayout :: proc() -> ClayArray(RenderCommand) ---
PointerOver :: proc(id: ElementId) -> bool ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions) ---
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
SetDebugModeEnabled :: proc(enabled: bool) ---
}
@(private, link_prefix = "Clay_", default_calling_convention = "c")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -15,7 +15,8 @@ checkImage3: raylib.Texture2D = {}
checkImage4: raylib.Texture2D = {}
checkImage5: raylib.Texture2D = {}
FONT_ID_TITLE_56 :: 0
FONT_ID_BODY_16 :: 0
FONT_ID_TITLE_56 :: 9
FONT_ID_TITLE_52 :: 1
FONT_ID_TITLE_48 :: 2
FONT_ID_TITLE_36 :: 3
@ -24,7 +25,6 @@ FONT_ID_BODY_36 :: 5
FONT_ID_BODY_30 :: 6
FONT_ID_BODY_28 :: 7
FONT_ID_BODY_24 :: 8
FONT_ID_BODY_16 :: 9
COLOR_LIGHT :: clay.Color{244, 235, 230, 255}
COLOR_LIGHT_HOVER :: clay.Color{224, 215, 210, 255}
@ -429,7 +429,7 @@ animationLerpValue: f32 = -1.0
createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
mobileScreen := windowWidth < 750
clay.BeginLayout(windowWidth, windowHeight)
clay.BeginLayout()
if clay.Rectangle(
clay.ID("OuterContainer"),
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}),
@ -494,7 +494,7 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
}
}
}
return clay.EndLayout(windowWidth, windowHeight)
return clay.EndLayout()
}
loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) {
@ -510,7 +510,7 @@ main :: proc() {
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.SetMeasureTextFunction(measureText)
clay.Initialize(arena)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()})
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
@ -533,6 +533,8 @@ main :: proc() {
checkImage4 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_4.png"))
checkImage5 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_5.png"))
debugModeEnabled: bool = false
for !raylib.WindowShouldClose() {
defer free_all(context.temp_allocator)
@ -542,8 +544,13 @@ main :: proc() {
}
windowWidth = raylib.GetScreenWidth()
windowHeight = raylib.GetScreenHeight()
if (raylib.IsKeyPressed(.D)) {
debugModeEnabled = !debugModeEnabled
clay.SetDebugModeEnabled(debugModeEnabled)
}
clay.SetPointerState(transmute(clay.Vector2)raylib.GetMousePosition(), raylib.IsMouseButtonDown(raylib.MouseButton.LEFT))
clay.UpdateScrollContainers(false, transmute(clay.Vector2)raylib.GetMouseWheelMoveV(), raylib.GetFrameTime())
clay.SetLayoutDimensions({cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()})
renderCommands: clay.ClayArray(clay.RenderCommand) = createLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue))
raylib.BeginDrawing()
clayRaylibRender(&renderCommands)

View file

@ -26,7 +26,7 @@ measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) ->
textHeight := cast(f32)config.fontSize
fontToUse := raylibFonts[config.fontId].font
for i in 0..<int(text.length) {
for i in 0 ..< int(text.length) {
if (text.chars[i] == '\n') {
maxTextWidth = max(maxTextWidth, lineTextWidth)
lineTextWidth = 0
@ -49,25 +49,25 @@ measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) ->
}
clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand), allocator := context.temp_allocator) {
for i in 0..<int(renderCommands.length) {
for i in 0 ..< int(renderCommands.length) {
renderCommand := clay.RenderCommandArray_Get(renderCommands, cast(i32)i)
boundingBox := renderCommand.boundingBox
switch (renderCommand.commandType) {
case clay.RenderCommandType.None:
{}
case clay.RenderCommandType.Text:
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
text := string(renderCommand.text.chars[:renderCommand.text.length])
cloned := strings.clone_to_cstring(text, allocator)
fontToUse: raylib.Font = raylibFonts[renderCommand.config.textElementConfig.fontId].font
raylib.DrawTextEx(
fontToUse,
cloned,
raylib.Vector2{boundingBox.x, boundingBox.y},
cast(f32)renderCommand.config.textElementConfig.fontSize,
cast(f32)renderCommand.config.textElementConfig.letterSpacing,
clayColorToRaylibColor(renderCommand.config.textElementConfig.textColor),
)
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
text := string(renderCommand.text.chars[:renderCommand.text.length])
cloned := strings.clone_to_cstring(text, allocator)
fontToUse: raylib.Font = raylibFonts[renderCommand.config.textElementConfig.fontId].font
raylib.DrawTextEx(
fontToUse,
cloned,
raylib.Vector2{boundingBox.x, boundingBox.y},
cast(f32)renderCommand.config.textElementConfig.fontSize,
cast(f32)renderCommand.config.textElementConfig.letterSpacing,
clayColorToRaylibColor(renderCommand.config.textElementConfig.textColor),
)
case clay.RenderCommandType.Image:
// TODO image handling
imageTexture := cast(^raylib.Texture2D)renderCommand.config.imageElementConfig.imageData
@ -85,20 +85,9 @@ clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand), al
config: ^clay.RectangleElementConfig = renderCommand.config.rectangleElementConfig
if (config.cornerRadius.topLeft > 0) {
radius: f32 = (config.cornerRadius.topLeft * 2) / min(boundingBox.width, boundingBox.height)
raylib.DrawRectangleRounded(
raylib.Rectangle{boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height},
radius,
8,
clayColorToRaylibColor(config.color),
)
raylib.DrawRectangleRounded(raylib.Rectangle{boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, radius, 8, clayColorToRaylibColor(config.color))
} else {
raylib.DrawRectangle(
cast(i32)boundingBox.x,
cast(i32)boundingBox.y,
cast(i32)boundingBox.width,
cast(i32)boundingBox.height,
clayColorToRaylibColor(config.color),
)
raylib.DrawRectangle(cast(i32)boundingBox.x, cast(i32)boundingBox.y, cast(i32)boundingBox.width, cast(i32)boundingBox.height, clayColorToRaylibColor(config.color))
}
case clay.RenderCommandType.Border:
config := renderCommand.config.borderElementConfig
@ -166,10 +155,7 @@ clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand), al
}
if (config.cornerRadius.bottomLeft > 0) {
raylib.DrawRing(
raylib.Vector2 {
math.round(boundingBox.x + config.cornerRadius.bottomLeft),
math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomLeft),
},
raylib.Vector2{math.round(boundingBox.x + config.cornerRadius.bottomLeft), math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomLeft)},
math.round(config.cornerRadius.bottomLeft - cast(f32)config.top.width),
config.cornerRadius.bottomLeft,
90,
@ -193,7 +179,7 @@ clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand), al
)
}
case clay.RenderCommandType.Custom:
// Implement custom element rendering here
// Implement custom element rendering here
}
}
}