diff --git a/bindings/odin/README.md b/bindings/odin/README.md index 579f0e9..263ee3b 100644 --- a/bindings/odin/README.md +++ b/bindings/odin/README.md @@ -1,6 +1,6 @@ ### 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 @@ -8,21 +8,21 @@ Special thanks to - [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): +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 -}); +// Define an element with 16px of x and y padding +CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) { + // 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 +if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) { + // Child elements here } ``` @@ -34,111 +34,170 @@ if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BO 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). +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). ```Odin -minMemorySize: u32 = clay.MinMemorySize() -memory := make([^]u8, minMemorySize) -arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory) -clay.Initialize(arena) +error_handler :: proc "c" (errorData: clay.ErrorData) { + // Do something with the error data. +} + +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 `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. +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. ```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 +measure_text :: proc "c" ( + text: clay.StringSlice, + 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 + return { + width = f32(text.length * i32(config.fontSize)), + height = f32(config.fontSize), + } } -// 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. +// Tell clay how to measure text +clay.SetMeasureTextFunction(measure_text, nil) +``` + +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. ```Odin // Update internal pointer position for handling mouseover / click / touch events -clay.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY }) +clay.SetPointerState( + clay.Vector2 { mouse_pos_x, mouse_pos_y }, + is_mouse_down, +) ``` -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. +5. Call [clay.BeginLayout()](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros. ```Odin +// Define some colors. COLOR_LIGHT :: clay.Color{224, 215, 210, 255} COLOR_RED :: clay.Color{168, 66, 28, 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 -sidebarItemLayout := clay.LayoutConfig { - sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)}, +sidebar_item_layout := 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})) {} +// Re-useable components are just normal procs. +sidebar_item_component :: proc(index: u32) { + if clay.UI()({ + id = clay.ID("SidebarBlob", index), + layout = sidebar_item_layout, + backgroundColor = 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 function to create your layout tree +create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) { + // Begin constructing the layout. + clay.BeginLayout() - // 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}})) + // 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.UI()({ + id = clay.ID("OuterContainer"), + layout = { + sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) }, + padding = { 16, 16, 16, 16 }, + childGap = 16, + }, + backgroundColor = { 250, 250, 255, 255 }, + }) { + if clay.UI()({ + id = clay.ID("SideBar"), + layout = { + layoutDirection = .TopToBottom, + sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, + padding = { 16, 16, 16, 16 }, + childGap = 16, + }, + backgroundColor = COLOR_LIGHT, + }) { + if clay.UI()({ + id = clay.ID("ProfilePictureOuter"), + 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 - for i in 0..<10 { - SidebarItemComponent(i) + // Standard Odin code like loops, etc. work inside components. + // Here we render 5 sidebar items. + for i in u32(0)..<5 { + sidebar_item_component(i) } } - if clay.Rectangle( - clay.ID("MainContent"), - clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}), - clay.RectangleConfig({color = COLOR_LIGHT}), - ) {} + if clay.UI()({ + id = clay.ID("MainContent"), + layout = { + sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) }, + }, + backgroundColor = COLOR_LIGHT, + }) {} } - // ... + + // Returns a list of render commands + render_commands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout() + return render_commands } ``` -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. +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. ```Odin -renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight) +render_commands := create_layout() -for i: u32 = 0; i < renderCommands.length; i += 1 { - renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i) +for i in 0.. `clay.Rectangle` (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_ID` (C) -> `clay.ID` (Odin).